Loading...
Searching...
No Matches
HWVulkanShared.hpp
1#pragma once
2
3#include <score/gfx/Vulkan.hpp>
4
5#if QT_HAS_VULKAN
6
7extern "C" {
8#if __has_include(<libavutil/hwcontext_vulkan.h>)
9#include <libavutil/hwcontext_vulkan.h>
10#define SCORE_HAS_VULKAN_HWCONTEXT_SHARED 1
11#endif
12}
13
14#if defined(SCORE_HAS_VULKAN_HWCONTEXT_SHARED) && QT_VERSION >= QT_VERSION_CHECK(6, 6, 0)
15
16#include <Gfx/Graph/decoders/ColorSpace.hpp>
17#include <Gfx/Graph/decoders/GPUVideoDecoder.hpp>
18#include <Gfx/Graph/decoders/NV12.hpp>
19#include <Gfx/Graph/decoders/P010.hpp>
20#include <Gfx/Graph/decoders/YUV420.hpp>
21#include <Gfx/Graph/decoders/YUV420P10.hpp>
22#include <Gfx/Graph/decoders/YUV422.hpp>
23#include <Gfx/Graph/decoders/YUV422P10.hpp>
24#include <Gfx/Graph/decoders/YUV444.hpp>
25#include <Gfx/Graph/decoders/YUV444P10.hpp>
26#include <Gfx/Graph/decoders/YUVA444.hpp>
27#include <Video/GpuFormats.hpp>
28
29// Qt private header for QVkTexture internals
30#include <QtGui/private/qrhivulkan_p.h>
31#include <qvulkanfunctions.h>
32#include <vulkan/vulkan.h>
33
34extern "C" {
35#include <libavformat/avformat.h>
36#include <libavutil/hwcontext.h>
37#include <libavutil/pixdesc.h>
38}
39
40namespace score::gfx
41{
42
53struct HWVulkanSharedDecoder : GPUVideoDecoder
54{
55 Video::ImageFormat& decoder;
56 PixelFormatInfo m_fmt;
57 int m_numPlanes{0};
58 // Codec-aligned dimensions of the decoded VkImages (0 = not yet known).
59 // When larger than the display size, texcoords are scaled so sampling
60 // stops at the conformance crop instead of covering the padding rows.
61 int m_codedW{0};
62 int m_codedH{0};
63
64 // Vulkan handles
65 VkDevice m_dev{VK_NULL_HANDLE};
66 VkPhysicalDevice m_physDev{VK_NULL_HANDLE};
67 QVulkanFunctions* m_funcs{};
68 QVulkanDeviceFunctions* m_dfuncs{};
69 PFN_vkWaitSemaphores m_vkWaitSemaphores{};
70 uint32_t m_gfxQueueFamilyIdx{0};
71 VkQueue m_gfxQueue{VK_NULL_HANDLE};
72
73 // Command infrastructure for custom multiplane barriers
74 VkCommandPool m_cmdPool{VK_NULL_HANDLE};
75 VkCommandBuffer m_cmdBuf{VK_NULL_HANDLE};
76 VkFence m_fence{VK_NULL_HANDLE};
77 bool m_cmdReady{false};
78
79 // Ring buffer for frame lifetime + deferred view destruction
80 static constexpr int NumSlots = 3;
81 struct FrameSlot
82 {
83 AVFrame* frameRef{};
84 VkImageView planeViews[4]{};
85 int numViews{0};
86 };
87 FrameSlot m_slots[NumSlots]{};
88 int m_slotIdx{0};
89
90 // ------------------------------------------------------------------
91
92 static bool isAvailable(QRhi& rhi)
93 {
94 if(rhi.backend() != QRhi::Vulkan)
95 return false;
96 auto* nh
97 = static_cast<const QRhiVulkanNativeHandles*>(rhi.nativeHandles());
98 if(!nh || !nh->dev || !nh->physDev || !nh->inst)
99 return false;
100 return nh->inst->getInstanceProcAddr("vkWaitSemaphores") != nullptr;
101 }
102
103 explicit HWVulkanSharedDecoder(
104 Video::ImageFormat& d, QRhi& rhi, PixelFormatInfo fmt, int codedW = 0,
105 int codedH = 0)
106 : decoder{d}
107 , m_fmt{fmt}
108 , m_codedW{codedW}
109 , m_codedH{codedH}
110 {
111 auto* nh
112 = static_cast<const QRhiVulkanNativeHandles*>(rhi.nativeHandles());
113 m_dev = nh->dev;
114 m_physDev = nh->physDev;
115 m_funcs = nh->inst->functions();
116 m_dfuncs = nh->inst->deviceFunctions(m_dev);
117 m_gfxQueueFamilyIdx = nh->gfxQueueFamilyIdx;
118 // vkWaitSemaphores is device-level: resolving it through
119 // getInstanceProcAddr yields a loader trampoline that crashes on the
120 // NVIDIA Windows driver. Go through vkGetDeviceProcAddr.
121 if(auto getDevProc = reinterpret_cast<PFN_vkGetDeviceProcAddr>(
122 nh->inst->getInstanceProcAddr("vkGetDeviceProcAddr")))
123 m_vkWaitSemaphores = reinterpret_cast<PFN_vkWaitSemaphores>(
124 getDevProc(m_dev, "vkWaitSemaphores"));
125 m_dfuncs->vkGetDeviceQueue(
126 m_dev, m_gfxQueueFamilyIdx, 0, &m_gfxQueue);
127 }
128
129 void release(RenderList& r) override
130 {
131 // The patched textures hold a VkImage owned by FFmpeg and a VkImageView
132 // owned by a ring slot; detach them so QVkTexture::destroy doesn't free
133 // resources it never owned (the slot views are destroyed in cleanupSlot).
134 for(auto& s : samplers)
135 if(auto* vkTex = static_cast<QVkTexture*>(s.texture); vkTex && !vkTex->owns)
136 {
137 vkTex->image = VK_NULL_HANDLE;
138 vkTex->imageView = VK_NULL_HANDLE;
139 }
140 GPUVideoDecoder::release(r);
141 }
142
143 ~HWVulkanSharedDecoder() override
144 {
145 // Wait for in-flight rendering to complete before destroying
146 // VkImageViews and freeing AVFrames (which release the VkImages).
147 // Without this, the last render pass command buffer may still
148 // reference these resources.
149 // Only wait on the graphics queue — lighter than vkDeviceWaitIdle.
150 if(m_gfxQueue != VK_NULL_HANDLE)
151 m_dfuncs->vkQueueWaitIdle(m_gfxQueue);
152
153 for(auto& slot : m_slots)
154 cleanupSlot(slot);
155 if(m_fence != VK_NULL_HANDLE)
156 m_dfuncs->vkDestroyFence(m_dev, m_fence, nullptr);
157 if(m_cmdPool != VK_NULL_HANDLE)
158 m_dfuncs->vkDestroyCommandPool(m_dev, m_cmdPool, nullptr);
159 }
160
161 void cleanupSlot(FrameSlot& slot)
162 {
163 for(int i = 0; i < slot.numViews; i++)
164 {
165 if(slot.planeViews[i] != VK_NULL_HANDLE)
166 {
167 m_dfuncs->vkDestroyImageView(m_dev, slot.planeViews[i], nullptr);
168 slot.planeViews[i] = VK_NULL_HANDLE;
169 }
170 }
171 slot.numViews = 0;
172 if(slot.frameRef)
173 {
174 av_frame_free(&slot.frameRef);
175 slot.frameRef = nullptr;
176 }
177 }
178
179 bool setupCommandInfra()
180 {
181 VkCommandPoolCreateInfo poolInfo{};
182 poolInfo.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO;
183 poolInfo.queueFamilyIndex = m_gfxQueueFamilyIdx;
184 poolInfo.flags = VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT;
185 if(m_dfuncs->vkCreateCommandPool(m_dev, &poolInfo, nullptr, &m_cmdPool)
186 != VK_SUCCESS)
187 return false;
188
189 VkCommandBufferAllocateInfo allocInfo{};
190 allocInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO;
191 allocInfo.commandPool = m_cmdPool;
192 allocInfo.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY;
193 allocInfo.commandBufferCount = 1;
194 if(m_dfuncs->vkAllocateCommandBuffers(m_dev, &allocInfo, &m_cmdBuf)
195 != VK_SUCCESS)
196 return false;
197
198 VkFenceCreateInfo fenceInfo{};
199 fenceInfo.sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO;
200 if(m_dfuncs->vkCreateFence(m_dev, &fenceInfo, nullptr, &m_fence)
201 != VK_SUCCESS)
202 return false;
203
204 m_cmdReady = true;
205 return true;
206 }
207
208 // ------------------------------------------------------------------
209 // init -- create placeholder textures and shaders
210 // ------------------------------------------------------------------
211
212 // vertexShader() with the conformance crop baked into the texcoords when
213 // the decoded images are larger than the display size.
214 QString cropVertexShader() const
215 {
216 QString vtx = vertexShader();
217 const int w = decoder.width, h = decoder.height;
218 if(m_codedW > w || m_codedH > h)
219 {
220 const double sx = m_codedW > w ? double(w) / m_codedW : 1.;
221 const double sy = m_codedH > h ? double(h) / m_codedH : 1.;
222 vtx.replace(
223 "v_texcoord = texcoord;",
224 QString("v_texcoord = texcoord * vec2(%1, %2);")
225 .arg(sx, 0, 'f', 9)
226 .arg(sy, 0, 'f', 9));
227 }
228 return vtx;
229 }
230
231 std::pair<QShader, QShader> init(RenderList& r) override
232 {
233 auto& rhi = *r.state.rhi;
234 const auto w = decoder.width, h = decoder.height;
235 const bool is10 = m_fmt.is10bit();
236 auto texFmt = is10 ? QRhiTexture::R16 : QRhiTexture::R8;
237 int chromaW = AV_CEIL_RSHIFT(w, m_fmt.log2ChromaW);
238 int chromaH = AV_CEIL_RSHIFT(h, m_fmt.log2ChromaH);
239
240 // Semi-planar (2 planes: Y + UV) vs planar (3 planes: Y + U + V)
241 // Vulkan Video always outputs multiplane images; the plane count
242 // depends on the sw_format negotiated with FFmpeg.
243 // Semi-planar: NV12, P010, P210, P410, etc.
244 // Planar: YUV420P, YUV422P, YUV444P, etc.
245 m_numPlanes = m_fmt.numPlanes;
246
247 if(m_numPlanes == 2)
248 {
249 auto uvFmt = is10 ? QRhiTexture::RG16 : QRhiTexture::RG8;
250 createTex(rhi, texFmt, w, h);
251 createTex(rhi, uvFmt, chromaW, chromaH);
252
253 if(is10)
255 r.state, cropVertexShader(),
256 QString(P010Decoder::frag).arg("").arg(colorMatrix(decoder)));
257 else
258 {
259 QString frag = NV12Decoder::nv12_filter_prologue;
260 frag += " vec3 yuv = vec3(y, u, v);\n";
261 frag += NV12Decoder::nv12_filter_epilogue;
263 r.state, cropVertexShader(),
264 frag.arg("").arg(colorMatrix(decoder)));
265 }
266 }
267 else if(m_fmt.hasAlpha)
268 {
269 // 4 planes: Y + U + V + A (YUVA444, YUVA444P10, YUVA444P12, etc.)
270 createTex(rhi, texFmt, w, h);
271 createTex(rhi, texFmt, chromaW, chromaH);
272 createTex(rhi, texFmt, chromaW, chromaH);
273 createTex(rhi, texFmt, w, h); // Alpha at full resolution
274
275 if(!is10)
276 {
278 r.state, cropVertexShader(),
279 QString(YUVA444Decoder::frag).arg("").arg(colorMatrix(decoder)));
280 }
281 else
282 {
283 // R16_UNORM samples as raw_value/65535. The 8-bit-equivalent code of
284 // an n-bit sample is code / 2^(n-8), so full scale is 255 * 2^(n-8).
285 double scale = 65535.0 / (255.0 * (1 << (m_fmt.bitDepth - 8)));
286 QString frag = QString(R"_(#version 450
287
288)_" SCORE_GFX_VIDEO_UNIFORMS R"_(
289
290layout(binding=3) uniform sampler2D y_tex;
291layout(binding=4) uniform sampler2D u_tex;
292layout(binding=5) uniform sampler2D v_tex;
293layout(binding=6) uniform sampler2D a_tex;
294
295layout(location = 0) in vec2 v_texcoord;
296layout(location = 0) out vec4 fragColor;
297
298%2
299
300vec4 processTexture(vec4 tex) {
301 vec4 processed = convert_to_rgb(tex);
302 { %1 }
303 return processed;
304}
305
306void main()
307{
308 float sc = float(%3);
309 float y = sc * texture(y_tex, v_texcoord).r;
310 float u = sc * texture(u_tex, v_texcoord).r;
311 float v = sc * texture(v_tex, v_texcoord).r;
312 float a = sc * texture(a_tex, v_texcoord).r;
313
314 vec4 rgb = processTexture(vec4(y,u,v, 1.));
315 fragColor = vec4(rgb.rgb, a);
316}
317)_").arg("").arg(colorMatrix(decoder)).arg(scale, 0, 'f', 6);
318
319 return score::gfx::makeShaders(r.state, cropVertexShader(), frag);
320 }
321 }
322 else
323 {
324 // 3 planes: Y + U + V (YUV420P, YUV422P, YUV444P, etc.)
325 createTex(rhi, texFmt, w, h);
326 createTex(rhi, texFmt, chromaW, chromaH);
327 createTex(rhi, texFmt, chromaW, chromaH);
328
329 if(!is10)
330 {
331 // YUV420Decoder::frag additionally parameterizes the chroma texture
332 // names (%3 / %4, for the YV12 swap); Vulkan Video always outputs
333 // U, V plane order.
334 if(m_fmt.log2ChromaW == 1 && m_fmt.log2ChromaH == 0)
336 r.state, cropVertexShader(),
337 QString(YUV422Decoder::frag).arg("").arg(colorMatrix(decoder)));
338 else if(m_fmt.log2ChromaW == 0 && m_fmt.log2ChromaH == 0)
340 r.state, cropVertexShader(),
341 QString(YUV444Decoder::frag).arg("").arg(colorMatrix(decoder)));
343 r.state, cropVertexShader(),
344 QString(YUV420Decoder::frag)
345 .arg("")
346 .arg(colorMatrix(decoder))
347 .arg("u")
348 .arg("v"));
349 }
350 else
351 {
352 // R16_UNORM: scale from actual bit depth to normalized range
353 double scale = 65535.0 / ((1 << m_fmt.bitDepth) - 1);
354 QString frag = QString(R"_(#version 450
355
356)_" SCORE_GFX_VIDEO_UNIFORMS R"_(
357
358layout(binding=3) uniform sampler2D y_tex;
359layout(binding=4) uniform sampler2D u_tex;
360layout(binding=5) uniform sampler2D v_tex;
361
362layout(location = 0) in vec2 v_texcoord;
363layout(location = 0) out vec4 fragColor;
364
365%2
366
367vec4 processTexture(vec4 tex) {
368 vec4 processed = convert_to_rgb(tex);
369 { %1 }
370 return processed;
371}
372
373void main()
374{
375 float sc = float(%3);
376 float y = sc * texture(y_tex, v_texcoord).r;
377 float u = sc * texture(u_tex, v_texcoord).r;
378 float v = sc * texture(v_tex, v_texcoord).r;
379
380 fragColor = processTexture(vec4(y,u,v, 1.));
381}
382)_").arg("").arg(colorMatrix(decoder)).arg(scale, 0, 'f', 6);
383
384 return score::gfx::makeShaders(r.state, cropVertexShader(), frag);
385 }
386 }
387 }
388
389 // ------------------------------------------------------------------
390 // exec -- per-plane VkImageViews on multiplane image (true zero-copy)
391 // ------------------------------------------------------------------
392
393 void exec(RenderList& r, QRhiResourceUpdateBatch& res, AVFrame& frame) override
394 {
395#if LIBAVUTIL_VERSION_MAJOR >= 57
396 if(!Video::formatIsHardwareDecoded(
397 static_cast<AVPixelFormat>(frame.format)))
398 return;
399
400 auto* vkf = reinterpret_cast<AVVkFrame*>(frame.data[0]);
401 if(!vkf || vkf->img[0] == VK_NULL_HANDLE)
402 return;
403
404 if(!m_cmdReady && !setupCommandInfra())
405 return;
406
407 // Wait on FFmpeg's timeline semaphores (host-side)
408 if(m_vkWaitSemaphores && vkf->sem[0] != VK_NULL_HANDLE)
409 {
410 int numSems = 0;
411 for(int i = 0; i < 4; i++)
412 if(vkf->sem[i] != VK_NULL_HANDLE)
413 numSems = i + 1;
414 if(numSems > 0)
415 {
416 VkSemaphoreWaitInfo waitInfo{};
417 waitInfo.sType = VK_STRUCTURE_TYPE_SEMAPHORE_WAIT_INFO;
418 waitInfo.semaphoreCount = static_cast<uint32_t>(numSems);
419 waitInfo.pSemaphores = vkf->sem;
420 waitInfo.pValues = vkf->sem_value;
421 m_vkWaitSemaphores(m_dev, &waitInfo, UINT64_MAX);
422 }
423 }
424
425 // Ring buffer management
426 auto& slot = m_slots[m_slotIdx];
427 cleanupSlot(slot);
428 m_slotIdx = (m_slotIdx + 1) % NumSlots;
429
430 slot.frameRef = av_frame_alloc();
431 if(av_frame_ref(slot.frameRef, &frame) < 0)
432 {
433 av_frame_free(&slot.frameRef);
434 slot.frameRef = nullptr;
435 return;
436 }
437
438 // Count separate VkImages
439 int numSrcImages = 0;
440 for(int i = 0; i < m_numPlanes; i++)
441 if(vkf->img[i] != VK_NULL_HANDLE)
442 numSrcImages++;
443
444 const bool isMultiplane = (numSrcImages == 1 && m_numPlanes > 1);
445
446 if(isMultiplane)
447 {
448 // --- Submit a custom barrier for the multiplane image ---
449 // QRhi would use VK_IMAGE_ASPECT_COLOR_BIT which is invalid for
450 // multiplane. We submit our own barrier with the correct plane aspects,
451 // transitioning to SHADER_READ_ONLY_OPTIMAL. Then we tell QRhi the
452 // image is already in that layout so it doesn't insert its own barrier.
453
454 m_dfuncs->vkResetCommandBuffer(m_cmdBuf, 0);
455
456 VkCommandBufferBeginInfo beginInfo{};
457 beginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO;
458 beginInfo.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT;
459 m_dfuncs->vkBeginCommandBuffer(m_cmdBuf, &beginInfo);
460
461 // One barrier per plane with the correct aspect mask
462 VkImageMemoryBarrier barriers[4]{};
463 static const VkImageAspectFlagBits planeAspects[] = {
464 VK_IMAGE_ASPECT_PLANE_0_BIT,
465 VK_IMAGE_ASPECT_PLANE_1_BIT,
466 VK_IMAGE_ASPECT_PLANE_2_BIT,
467 };
468
469 for(int i = 0; i < m_numPlanes && i < 3; i++)
470 {
471 barriers[i].sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER;
472 barriers[i].srcAccessMask = VK_ACCESS_MEMORY_WRITE_BIT;
473 barriers[i].dstAccessMask = VK_ACCESS_SHADER_READ_BIT;
474 barriers[i].oldLayout = vkf->layout[0];
475 barriers[i].newLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
476 barriers[i].srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
477 barriers[i].dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
478 barriers[i].image = vkf->img[0];
479 barriers[i].subresourceRange.aspectMask = planeAspects[i];
480 barriers[i].subresourceRange.baseMipLevel = 0;
481 barriers[i].subresourceRange.levelCount = 1;
482 barriers[i].subresourceRange.baseArrayLayer = 0;
483 barriers[i].subresourceRange.layerCount = 1;
484 }
485
486 m_dfuncs->vkCmdPipelineBarrier(
487 m_cmdBuf,
488 VK_PIPELINE_STAGE_ALL_COMMANDS_BIT,
489 VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT,
490 0, 0, nullptr, 0, nullptr,
491 static_cast<uint32_t>(m_numPlanes), barriers);
492
493 m_dfuncs->vkEndCommandBuffer(m_cmdBuf);
494
495 m_dfuncs->vkResetFences(m_dev, 1, &m_fence);
496 VkSubmitInfo submitInfo{};
497 submitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO;
498 submitInfo.commandBufferCount = 1;
499 submitInfo.pCommandBuffers = &m_cmdBuf;
500 m_dfuncs->vkQueueSubmit(m_gfxQueue, 1, &submitInfo, m_fence);
501 m_dfuncs->vkWaitForFences(m_dev, 1, &m_fence, VK_TRUE, UINT64_MAX);
502
503 // --- Create per-plane VkImageViews and patch QVkTexture ---
504 for(int i = 0; i < m_numPlanes && i < (int)samplers.size(); i++)
505 {
506 auto* vkTex = static_cast<QVkTexture*>(samplers[i].texture);
507
508 VkImageViewCreateInfo viewInfo{};
509 viewInfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO;
510 viewInfo.image = vkf->img[0];
511 viewInfo.viewType = VK_IMAGE_VIEW_TYPE_2D;
512 viewInfo.format = vkTex->vkformat;
513 viewInfo.components = {
514 VK_COMPONENT_SWIZZLE_IDENTITY,
515 VK_COMPONENT_SWIZZLE_IDENTITY,
516 VK_COMPONENT_SWIZZLE_IDENTITY,
517 VK_COMPONENT_SWIZZLE_IDENTITY};
518 viewInfo.subresourceRange.aspectMask = planeAspects[i];
519 viewInfo.subresourceRange.baseMipLevel = 0;
520 viewInfo.subresourceRange.levelCount = 1;
521 viewInfo.subresourceRange.baseArrayLayer = 0;
522 viewInfo.subresourceRange.layerCount = 1;
523
524 VkImageView planeView = VK_NULL_HANDLE;
525 if(m_dfuncs->vkCreateImageView(
526 m_dev, &viewInfo, nullptr, &planeView)
527 != VK_SUCCESS)
528 return;
529
530 slot.planeViews[i] = planeView;
531 slot.numViews = i + 1;
532
533 // Destroy QRhi's own placeholder view the first time only. On later
534 // frames vkTex->imageView is a ring slot's plane view: it is owned
535 // by that slot and destroyed in cleanupSlot when the slot recycles,
536 // two frames later — destroying it here as well double-frees it,
537 // which crashes the NVIDIA Windows driver.
538 if(vkTex->owns && vkTex->imageView != VK_NULL_HANDLE)
539 m_dfuncs->vkDestroyImageView(m_dev, vkTex->imageView, nullptr);
540
541 vkTex->image = vkf->img[0];
542 vkTex->imageView = planeView;
543 vkTex->owns = false;
544 // Already transitioned to SHADER_READ_ONLY by our barrier
545 vkTex->usageState.layout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
546 vkTex->usageState.access = VK_ACCESS_SHADER_READ_BIT;
547 vkTex->usageState.stage = VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT;
548 vkTex->generation++;
549 }
550 }
551 else
552 {
553 // Separate per-plane VkImages: direct createFrom() wrapping
554 for(int i = 0; i < m_numPlanes && i < (int)samplers.size(); i++)
555 {
556 if(vkf->img[i] != VK_NULL_HANDLE)
557 {
558 samplers[i].texture->createFrom(QRhiTexture::NativeTexture{
559 quint64(vkf->img[i]), int(vkf->layout[i])});
560 }
561 }
562 }
563#endif
564 }
565
566private:
567 void createTex(QRhi& rhi, QRhiTexture::Format fmt, int w, int h)
568 {
569 auto tex = rhi.newTexture(fmt, {w, h}, 1, QRhiTexture::Flag{});
570 tex->create();
571 auto sampler = rhi.newSampler(
572 QRhiSampler::Linear, QRhiSampler::Linear, QRhiSampler::None,
573 QRhiSampler::ClampToEdge, QRhiSampler::ClampToEdge);
574 sampler->create();
575 samplers.push_back({sampler, tex});
576 }
577};
578
579} // namespace score::gfx
580
581#endif // SCORE_HAS_VULKAN_HWCONTEXT_SHARED && QT_VERSION >= 6.6
582#endif // QT_HAS_VULKAN
Graphics rendering pipeline for ossia score.
Definition Filter/PreviewWidget.hpp:11
std::pair< QShader, QShader > makeShaders(const RenderState &v, QString vert, QString frag, int multiViewCount)
Get a pair of compiled vertex / fragment shaders from GLSL 4.5 sources.
Definition score-plugin-gfx/Gfx/Graph/Utils.cpp:1238
Definition VideoInterface.hpp:26