MythTV master
mythpaintervulkan.cpp
Go to the documentation of this file.
1// C++
2#include <algorithm>
3
4// Qt
5#include <QGuiApplication>
6
7// MythTV
9#include "mythimage.h"
17
18#define LOC QString("VulkanPainter: ")
19
21 : MythPainterGPU(Parent)
22{
23 m_transforms.push(QMatrix4x4());
25}
26
28{
30 m_vulkan->Funcs()->vkQueueWaitIdle(m_vulkan->Window()->graphicsQueue());
31
32 Teardown();
35}
36
38{
39 ClearCache();
42}
43
46{
48 m_vulkan->Funcs()->vkQueueWaitIdle(m_vulkan->Window()->graphicsQueue());
49
50 LOG(VB_GENERAL, LOG_INFO, LOC + "Releasing Vulkan resources");
51
53 delete m_textureShader;
54
56 {
57 m_vulkan->Funcs()->vkDestroyPipeline(m_vulkan->Device(), m_texturePipeline, nullptr);
58 m_vulkan->Funcs()->vkDestroyDescriptorPool(m_vulkan->Device(), m_textureDescriptorPool, nullptr);
59 m_vulkan->Funcs()->vkDestroyDescriptorPool(m_vulkan->Device(), m_projectionDescriptorPool, nullptr);
60 m_vulkan->Funcs()->vkDestroySampler(m_vulkan->Device(), m_textureSampler, nullptr);
62 {
63 m_vulkan->Funcs()->vkFreeCommandBuffers(m_vulkan->Device(),
64 m_vulkan->Window()->graphicsCommandPool(), 1, &m_textureUploadCmd);
65 }
66 }
67
68 m_ready = false;
69 delete m_vulkan;
70 m_vulkan = nullptr;
72 m_projectionDescriptor = MYTH_NULL_DISPATCH; // destroyed with pool
73 m_projectionUniform = nullptr;
74 m_textureShader = nullptr;
75 m_textureUploadCmd = nullptr;
80 m_availableTextureDescriptors.clear(); // destroyed with pool
81 m_frameStarted = false;
82 m_lastSize = { 0, 0 };
83
84 LOG(VB_GENERAL, LOG_INFO, LOC + "Finished releasing resources");
85}
86
88{
89 return "Vulkan";
90}
91
93{
94 return true;
95}
96
98{
99 return true;
100}
101
103{
104 return false;
105}
106
108{
109 QMatrix4x4 newtop = m_transforms.top();
110 if (Fx.m_hzoom != 1.0F || Fx.m_vzoom != 1.0F || Fx.m_angle != 0.0F)
111 {
112 newtop.translate(static_cast<float>(Center.x()), static_cast<float>(Center.y()));
113 newtop.scale(Fx.m_hzoom, Fx.m_vzoom);
114 newtop.rotate(Fx.m_angle, 0, 0, 1);
115 newtop.translate(static_cast<float>(-Center.x()), static_cast<float>(-Center.y()));
116 }
117 m_transforms.push(newtop);
118}
119
121{
122 m_transforms.pop();
123}
124
126{
127 if (m_ready)
128 return true;
129
130 if (!m_vulkan)
131 {
132 // device setup can be delayed by a few frames on startup - check status
133 // before continuing
135 if (!window || !window->device())
136 return false;
137
139 if (!m_vulkan)
140 return false;
141 }
142
143 if (!m_textureShader)
144 {
145 std::vector<int> stages = { DefaultVertex450, DefaultFragment450 };
147 if (!m_textureShader)
148 return false;
149 }
150
152 {
153 QRect viewport(QPoint{0, 0}, m_vulkan->Window()->swapChainImageSize());
156 return false;
157 }
158
160 {
161 const auto & sizes = m_textureShader->GetPoolSizes(0);
162 VkDescriptorPoolCreateInfo pool { VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO, nullptr,
163 0, 1, static_cast<uint32_t>(sizes.size()), sizes.data() };
164 if (m_vulkan->Funcs()->vkCreateDescriptorPool(m_vulkan->Device(), &pool, nullptr, &m_projectionDescriptorPool) != VK_SUCCESS)
165 {
166 LOG(VB_GENERAL, LOG_ERR, LOC + "Failed to create descriptor pool for projection");
167 return false;
168 }
169 }
170
172 {
175 return false;
176
177 m_projection.setToIdentity();
178 QRect viewport(QPoint{0, 0}, m_vulkan->Window()->swapChainImageSize());
179 m_projection.ortho(viewport);
180 m_projection = m_vulkan->Window()->clipCorrectionMatrix() * m_projection;
182 }
183
185 {
186 // projection is set 0
187 VkDescriptorSetLayout layout = m_textureShader->GetDescSetLayout(0);
188 VkDescriptorSetAllocateInfo alloc { };
189 alloc.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO;
190 alloc.descriptorPool = m_projectionDescriptorPool;
191 alloc.descriptorSetCount = 1;
192 alloc.pSetLayouts = &layout;
193
194 if (m_vulkan->Funcs()->vkAllocateDescriptorSets(m_vulkan->Device(), &alloc, &m_projectionDescriptor) != VK_SUCCESS)
195 {
196 LOG(VB_GENERAL, LOG_INFO, LOC + "Failed to allocate projection descriptor set");
197 return false;
198 }
199
200 VkDescriptorBufferInfo buffdesc = m_projectionUniform->GetBufferInfo();
201
202 VkWriteDescriptorSet write { };
203 write.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
205 write.dstBinding = 0;
206 write.dstArrayElement = 0;
207 write.descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER;
208 write.descriptorCount = 1;
209 write.pBufferInfo = &buffdesc;
210
211 m_vulkan->Funcs()->vkUpdateDescriptorSets(m_vulkan->Device(), 1, &write, 0, nullptr);
212 }
213
215 {
216 const auto & sizes = m_textureShader->GetPoolSizes(1);
217 // match total number of individual descriptors with pool size
218 std::vector<VkDescriptorPoolSize> adjsizes;
219 std::transform(sizes.cbegin(), sizes.cend(), std::back_inserter(adjsizes),
220 [](VkDescriptorPoolSize Size){ return VkDescriptorPoolSize { Size.type, MAX_TEXTURE_COUNT }; });
221 VkDescriptorPoolCreateInfo pool { VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO, nullptr,
222 0, MAX_TEXTURE_COUNT, static_cast<uint32_t>(adjsizes.size()), adjsizes.data() };
223 if (m_vulkan->Funcs()->vkCreateDescriptorPool(m_vulkan->Device(), &pool, nullptr, &m_textureDescriptorPool) != VK_SUCCESS)
224 {
225 LOG(VB_GENERAL, LOG_ERR, LOC + "Failed to create descriptor pool");
226 return false;
227 }
228 }
229
230 if (!m_textureDescriptorsCreated)
231 {
232 // transform and sampler are set 1 (projection is set 0)
233 VkDescriptorSetLayout layout = m_textureShader->GetDescSetLayout(1);
234 m_availableTextureDescriptors.clear();
235 for (int i = 0; i < MAX_TEXTURE_COUNT; ++i)
236 {
237 VkDescriptorSet descset = MYTH_NULL_DISPATCH;
238 VkDescriptorSetAllocateInfo alloc { };
239 alloc.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO;
240 alloc.descriptorPool = m_textureDescriptorPool;
241 alloc.descriptorSetCount = 1;
242 alloc.pSetLayouts = &layout;
243 VkResult res = m_vulkan->Funcs()->vkAllocateDescriptorSets(m_vulkan->Device(), &alloc, &descset);
244 if (res != VK_SUCCESS)
245 LOG(VB_GENERAL, LOG_ERR, LOC + "Failed to allocate descriptor set");
246 m_availableTextureDescriptors.push_back(descset);
247 }
248
249 m_textureDescriptorsCreated = true;
250 }
251
252 if (!m_textureSampler)
253 {
254 m_textureSampler = m_vulkan->Render()->CreateSampler(VK_FILTER_LINEAR, VK_FILTER_LINEAR);
255 if (!m_textureSampler)
256 return false;
257 }
258
259 m_ready = true;
260 return true;
261}
262
270void MythPainterVulkan::Begin(QPaintDevice* /*Parent*/)
271{
272 if (!Ready())
273 return;
274
275 // check if we need to adjust cache sizes
276 if (m_lastSize != m_vulkan->Window()->size())
277 {
278 // This will scale the cache depending on the resolution in use
279 static const int s_onehd = 1920 * 1080;
280 static const int s_basesize = 64;
281 m_lastSize = m_vulkan->Window()->size();
282 float hdscreens = (static_cast<float>(m_lastSize.width() + 1) * m_lastSize.height()) / s_onehd;
283 int cpu = std::max(static_cast<int>(hdscreens * s_basesize), s_basesize);
284 int gpu = cpu * 3 / 2;
285 SetMaximumCacheSizes(gpu, cpu);
286 }
287
288 // Sometimes the UI engine will mark images as 'changed' when moving between
289 // screens. These are then often released here whilst still in use for the
290 // previous frame. To avoid validation errors, wait for the last frame to
291 // complete before continuing - though this feels like a hack and surely
292 // the last frame should already be complete at this point?
293 if (!m_texturesToDelete.empty())
294 {
295 m_vulkan->Funcs()->vkQueueWaitIdle(m_vulkan->Window()->graphicsQueue());
297 }
298 m_frameStarted = true;
299}
300
302{
303 if (!(Ready() && m_frameStarted))
304 return;
305
306 // Complete any texture updates first
308 {
310 m_textureUploadCmd = nullptr;
311
312 // release staging buffers which are no longer needed
313 for (auto * texture : m_stagedTextures)
314 texture->StagingFinished();
315 m_stagedTextures.clear();
316 }
317
318 if (m_queuedTextures.empty())
319 return;
320
321 if (m_viewControl.testFlag(Framebuffer))
322 {
323 // Tell the renderer that we are requesting a frame start
325
326 // Signal DIRECTLY to the window to start the frame - which ensures
327 // the event is not delayed and we can start to render immediately.
328 QEvent update(QEvent::UpdateRequest);
329 QGuiApplication::sendEvent(m_vulkan->Window(), &update);
330 }
331
332 // Retrieve the command buffer
333 VkCommandBuffer currentcmdbuf = m_vulkan->Window()->currentCommandBuffer();
334 if (VERBOSE_LEVEL_CHECK(VB_GPU, LOG_INFO))
335 m_vulkan->Render()->BeginDebugRegion(currentcmdbuf, "PAINTER_RENDER", MythDebugVulkan::kDebugGreen);
336
337 // Bind our pipeline and retrieve layout once
338 m_vulkan->Funcs()->vkCmdBindPipeline(currentcmdbuf, VK_PIPELINE_BIND_POINT_GRAPHICS, m_texturePipeline);
339 VkPipelineLayout layout = m_textureShader->GetPipelineLayout();
340
341 // Bind descriptor set 0 - which is the projection, which is 'constant' for all textures
342 m_vulkan->Funcs()->vkCmdBindDescriptorSets(currentcmdbuf, VK_PIPELINE_BIND_POINT_GRAPHICS,
343 layout, 0, 1, &m_projectionDescriptor, 0, nullptr);
344
345 for (auto * texture : m_queuedTextures)
346 {
347 // Bind descriptor set 1 for this texture - sampler
348 m_vulkan->Funcs()->vkCmdBindDescriptorSets(currentcmdbuf, VK_PIPELINE_BIND_POINT_GRAPHICS,
349 layout, 1, 1, &texture->m_descriptor, 0, nullptr);
350
351 // Push constants - transform, vertex data and color (alpha)
352 m_vulkan->Funcs()->vkCmdPushConstants(currentcmdbuf, layout, VK_SHADER_STAGE_VERTEX_BIT,
353 0, MYTH_PUSHBUFFER_SIZE, texture->Data());
354 texture->PopData();
355
356 // Draw
357 m_vulkan->Funcs()->vkCmdDraw(currentcmdbuf, 4, 1, 0, 0);
358 }
359
360 if (VERBOSE_LEVEL_CHECK(VB_GPU, LOG_INFO))
361 m_vulkan->Render()->EndDebugRegion(currentcmdbuf);
362
363 m_queuedTextures.clear();
364
365 if (m_viewControl.testFlag(Framebuffer))
367}
368
369void MythPainterVulkan::DrawImage(const QRect Dest, MythImage *Image, const QRect Source, int Alpha)
370{
371 if (!m_frameStarted)
372 return;
373
374 MythTextureVulkan* texture = GetTextureFromCache(Image);
375 if (texture)
376 {
377 // Update push constant buffer
378 texture->PushData(m_transforms.top(), Source, Dest, Alpha);
379 // Queue
380 m_queuedTextures.emplace_back(texture);
381 }
382}
383
385{
386 return new MythImage(this);
387}
388
390{
391 if (m_imageToTextureMap.contains(Image))
392 {
393 m_texturesToDelete.push_back(m_imageToTextureMap[Image]);
394 m_imageToTextureMap.remove(Image);
395 m_imageExpire.remove(Image);
396 }
397}
398
400{
401 LOG(VB_GENERAL, LOG_INFO, "Clearing Vulkan painter cache.");
402
403 QMapIterator<MythImage *, MythTextureVulkan*> it(m_imageToTextureMap);
404 while (it.hasNext())
405 {
406 it.next();
407 m_texturesToDelete.push_back(m_imageToTextureMap[it.key()]);
408 m_imageExpire.remove(it.key());
409 }
410 m_imageToTextureMap.clear();
411}
412
414{
415 if (!(Ready() && Image))
416 return nullptr;
417
418 if (m_imageToTextureMap.contains(Image))
419 {
420 if (!Image->IsChanged())
421 {
422 m_imageExpire.remove(Image);
423 m_imageExpire.push_back(Image);
424 return m_imageToTextureMap[Image];
425 }
427 }
428
429 Image->SetChanged(false);
430
431 int count = 0; // guard against unexpected texture creation failure
432 MythTextureVulkan* texture = nullptr;
433 while (texture == nullptr)
434 {
438 if (texture != nullptr)
439 break;
440
441 // This can happen if the cached textures are too big for GPU memory
442 if ((count++ > 1000) || m_hardwareCacheSize < (8 * 1024 * 1024))
443 {
444 LOG(VB_GENERAL, LOG_ERR, "Failed to create Vulkan texture.");
445 return nullptr;
446 }
447
448 // Shrink the cache size
450 LOG(VB_GENERAL, LOG_NOTICE, LOC + QString("Shrinking HW cache size to %1KB")
451 .arg(m_maxHardwareCacheSize / 1024));
452
454 {
455 MythImage *expired = m_imageExpire.front();
456 m_imageExpire.pop_front();
457 DeleteFormatImagePriv(expired);
459 }
460 }
461
463 {
464 LOG(VB_GENERAL, LOG_ERR, LOC + "No descriptor pool?");
465 delete texture;
466 return nullptr;
467 }
468
469 // With a reasonable hardware cache size and many small thumbnails and
470 // text images, we can easily hit the max texture count before the cache
471 // size. So ensure we always have a descriptor available.
472
474 {
475 MythImage *expired = m_imageExpire.front();
476 m_imageExpire.pop_front();
477 DeleteFormatImagePriv(expired);
479
481 {
482 LOG(VB_GENERAL, LOG_WARNING, LOC + "No texture descriptor available??");
483 delete texture;
484 return nullptr;
485 }
486 }
487
488 VkDescriptorSet descset = m_availableTextureDescriptors.back();
490
491 auto imagedesc = texture->GetDescriptorImage();
492 VkWriteDescriptorSet write { };
493 write.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
494 write.dstSet = descset;
495 write.dstBinding = 0;
496 write.dstArrayElement = 0;
497 write.descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
498 write.descriptorCount = 1;
499 write.pImageInfo = &imagedesc;
500 m_vulkan->Funcs()->vkUpdateDescriptorSets(m_vulkan->Device(), 1, &write, 0, nullptr);
501 texture->AddDescriptor(descset);
502 m_stagedTextures.emplace_back(texture);
503
504 CheckFormatImage(Image);
506 m_imageToTextureMap[Image] = texture;
507 m_imageExpire.push_back(Image);
508
510 {
511 MythImage *expired = m_imageExpire.front();
512 m_imageExpire.pop_front();
513 DeleteFormatImagePriv(expired);
515 }
516
517 //LOG(VB_GENERAL, LOG_INFO, LOC + QString("Images: %1 Sets: %2 Recycled: %3")
518 // .arg(m_imageToTextureMap.size()).arg(m_allocatedTextureDescriptors).arg(m_availableTextureDescriptors.size()));
519 //LOG(VB_GENERAL, LOG_INFO, LOC + QString("Hardware cache: %1 (Max: %2)")
520 // .arg(m_hardwareCacheSize / (1024 * 1024)).arg(m_maxHardwareCacheSize / (1024 * 1024)));
521
522 return texture;
523}
524
526{
527 if (!Ready() || m_texturesToDelete.empty())
528 return;
529
530 while (!m_texturesToDelete.empty())
531 {
532 MythTextureVulkan *texture = m_texturesToDelete.front();
534 VkDescriptorSet descriptor = texture->TakeDescriptor();
535 if (descriptor)
536 m_availableTextureDescriptors.emplace_back(descriptor);
537 delete texture;
538 m_texturesToDelete.pop_front();
539 }
540}
void PushData(const QMatrix4x4 &Transform, QRect Source, QRect Destination, int Alpha)
static const MythVulkan4F kDebugGreen
bool IsChanged() const
Definition: mythimage.h:51
virtual void SetChanged(bool change=true)
Definition: mythimage.h:50
ViewControls m_viewControl
void DeleteFormatImagePriv(MythImage *Image) override
MythImage * GetFormatImagePriv() override
Creates a reference counted image, call DecrRef() to delete.
MythUniformBufferVulkan * m_projectionUniform
QString GetName() override
void DrawImage(QRect Dest, MythImage *Image, QRect Source, int Alpha) override
QStack< QMatrix4x4 > m_transforms
MythTextureVulkan * GetTextureFromCache(MythImage *Image)
void Begin(QPaintDevice *) override
Begin painting.
bool SupportsAnimation() override
std::vector< VkDescriptorSet > m_availableTextureDescriptors
VkDescriptorPool m_textureDescriptorPool
void DoFreeResources()
Free resources before the render device is released.
void PopTransformation() override
VkDescriptorSet m_projectionDescriptor
MythPainterVulkan(MythRenderVulkan *VulkanRender, MythMainWindow *Parent)
bool SupportsAlpha() override
QMap< MythImage *, MythTextureVulkan * > m_imageToTextureMap
QVector< MythTextureVulkan * > m_texturesToDelete
std::vector< MythTextureVulkan * > m_queuedTextures
bool SupportsClipping() override
VkSampler m_textureSampler
std::vector< MythTextureVulkan * > m_stagedTextures
VkDescriptorPool m_projectionDescriptorPool
VkCommandBuffer m_textureUploadCmd
VkPipeline m_texturePipeline
std::list< MythImage * > m_imageExpire
void FreeResources() override
~MythPainterVulkan() override
MythVulkanObject * m_vulkan
MythShaderVulkan * m_textureShader
void PushTransformation(const UIEffects &Fx, QPointF Center=QPointF()) override
int m_hardwareCacheSize
Definition: mythpainter.h:136
void CheckFormatImage(MythImage *im)
void SetMaximumCacheSizes(int hardware, int software)
int m_maxHardwareCacheSize
Definition: mythpainter.h:137
virtual void Teardown(void)
Definition: mythpainter.cpp:27
virtual void FreeResources(void)
Definition: mythpainter.h:53
MythWindowVulkan * GetVulkanWindow(void)
static MythRenderVulkan * GetVulkanRender(void)
void FinishSingleUseCommandBuffer(VkCommandBuffer &Buffer)
void EndDebugRegion(VkCommandBuffer CommandBuffer)
VkPipeline CreatePipeline(MythShaderVulkan *Shader, QRect Viewport, std::vector< VkDynamicState > Dynamic={ })
VkCommandBuffer CreateSingleUseCommandBuffer(void)
void SetFrameExpected(void)
void DoFreeResources(void)
void BeginDebugRegion(VkCommandBuffer CommandBuffer, const char *Name, MythVulkan4F Color)
const std::vector< VkDescriptorPoolSize > & GetPoolSizes(size_t Set) const
VkDescriptorSetLayout GetDescSetLayout(size_t Set) const
static MythShaderVulkan * Create(MythVulkanObject *Vulkan, const std::vector< int > &Stages, const MythShaderMap *Sources=nullptr, const MythBindingMap *Bindings=nullptr)
VkPipelineLayout GetPipelineLayout(void) const
VkDescriptorImageInfo GetDescriptorImage(void) const
void AddDescriptor(VkDescriptorSet Descriptor)
VkDescriptorSet TakeDescriptor(void)
static MythTextureVulkan * Create(MythVulkanObject *Vulkan, QImage *Image, VkSampler Sampler, VkCommandBuffer CommandBuffer=nullptr)
static MythUniformBufferVulkan * Create(MythVulkanObject *Vulkan, VkDeviceSize Size)
VkDescriptorBufferInfo GetBufferInfo(void) const
MythRenderVulkan * Render()
bool IsValidVulkan() const
MythWindowVulkan * Window()
QVulkanDeviceFunctions * Funcs()
static MythVulkanObject * Create(MythRenderVulkan *Render)
#define MYTH_PUSHBUFFER_SIZE
static bool VERBOSE_LEVEL_CHECK(uint64_t mask, LogLevel_t level)
Definition: mythlogging.h:29
#define LOG(_MASK_, _LEVEL_, _QSTRING_)
Definition: mythlogging.h:39
#define LOC
#define MAX_TEXTURE_COUNT
#define MYTH_NULL_DISPATCH
#define DefaultVertex450
#define DefaultFragment450
def write(text, progress=True)
Definition: mythburn.py:307