106 std::unique_ptr<QRhiBuffer> &uniformBuffer,
107 std::unique_ptr<QRhiSampler> &textureSampler,
108 std::unique_ptr<QRhiShaderResourceBindings> &shaderResourceBindings,
109 std::unique_ptr<QRhiGraphicsPipeline> &graphicsPipeline,
110 std::unique_ptr<QRhiRenderPassDescriptor> &renderPass,
112 const QVideoFrameTexturesUPtr &videoFrameTextures)
114 auto format = frame.surfaceFormat();
115 auto pixelFormat = format.pixelFormat();
117 auto textureDesc = QVideoTextureHelper::textureDescription(pixelFormat);
121 *b++ = QRhiShaderResourceBinding::uniformBuffer(0, QRhiShaderResourceBinding::VertexStage | QRhiShaderResourceBinding::FragmentStage,
122 uniformBuffer.get());
123 for (
int i = 0; i < textureDesc->nplanes; ++i)
124 *b++ = QRhiShaderResourceBinding::sampledTexture(i + 1, QRhiShaderResourceBinding::FragmentStage,
125 videoFrameTextures->texture(i), textureSampler.get());
126 shaderResourceBindings->setBindings(bindings, b);
127 if (!shaderResourceBindings->create()) {
128 qCDebug(qLcVideoFrameConverter)
129 << Q_FUNC_INFO <<
": failed to create shader resource bindings";
133 graphicsPipeline.reset(rhi->newGraphicsPipeline());
134 graphicsPipeline->setTopology(QRhiGraphicsPipeline::TriangleStrip);
136 QShader vs = ensureShader(QVideoTextureHelper::vertexShaderFileName(format));
140 QShader fs = ensureShader(QVideoTextureHelper::fragmentShaderFileName(format, rhi));
144 graphicsPipeline->setShaderStages({
145 { QRhiShaderStage::Vertex, vs },
146 { QRhiShaderStage::Fragment, fs }
150 inputLayout.setBindings({
151 { 4 *
sizeof(
float) }
153 inputLayout.setAttributes({
154 { 0, 0, QRhiVertexInputAttribute::Float2, 0 },
155 { 0, 1, QRhiVertexInputAttribute::Float2, 2 *
sizeof(
float) }
158 graphicsPipeline->setVertexInputLayout(inputLayout);
159 graphicsPipeline->setShaderResourceBindings(shaderResourceBindings.get());
160 graphicsPipeline->setRenderPassDescriptor(renderPass.get());
161 if (!graphicsPipeline->create()) {
162 qCDebug(qLcVideoFrameConverter) << Q_FUNC_INFO <<
": failed to create graphics pipeline";
171 QVideoFrame varFrame = frame;
172 if (!varFrame.map(QVideoFrame::ReadOnly)) {
173 qCDebug(qLcVideoFrameConverter) << Q_FUNC_INFO <<
": frame mapping failed";
177 auto unmap =
std::optional(QScopeGuard([&] {
181 QSpan<uchar> jpegData{
183 varFrame.mappedBytes(0),
188 constexpr std::array<uchar, 2> soiMarker{ uchar(0xff), uchar(0xd8) };
189 if (!ranges::equal(take(jpegData, 2), soiMarker, std::equal_to<
void>{})) {
190 qCDebug(qLcVideoFrameConverter)
191 << Q_FUNC_INFO <<
": JPEG data does not start with SOI marker";
195 constexpr std::array<uchar, 2> eoiMarker{ uchar(0xff), uchar(0xd9) };
199 if (!ranges::equal(jpegData.last(2), eoiMarker, std::equal_to<
void>{})) {
200 qCDebug(qLcVideoFrameConverter)
201 << Q_FUNC_INFO <<
": JPEG data does not end with EOI marker";
203 auto eoi_it = std::find_end(jpegData.begin(), jpegData.end(), std::begin(eoiMarker),
204 std::end(eoiMarker));
205 if (eoi_it == jpegData.end()) {
206 qCWarning(qLcVideoFrameConverter)
207 << Q_FUNC_INFO <<
": JPEG data does not contain EOI marker";
211 const size_t newSize =
std::distance(jpegData.begin(), eoi_it) +
std::size(eoiMarker);
212 jpegData = jpegData.first(newSize);
215 QImage image = QImage::fromData(jpegData,
"JPG");
216 unmap =
std::nullopt;
253 QMacAutoReleasePool releasePool;
256 std::unique_ptr<QRhiRenderPassDescriptor> renderPass;
257 std::unique_ptr<QRhiBuffer> vertexBuffer;
258 std::unique_ptr<QRhiBuffer> uniformBuffer;
259 std::unique_ptr<QRhiTexture> targetTexture;
260 std::unique_ptr<QRhiTextureRenderTarget> renderTarget;
261 std::unique_ptr<QRhiSampler> textureSampler;
262 std::unique_ptr<QRhiShaderResourceBindings> shaderResourceBindings;
263 std::unique_ptr<QRhiGraphicsPipeline> graphicsPipeline;
265 if (frame.size().isEmpty() || frame.pixelFormat() == QVideoFrameFormat::Format_Invalid)
268 if (frame.pixelFormat() == QVideoFrameFormat::Format_Jpeg)
269 return convertJPEG(frame, transformation);
272 return convertCPU(frame, transformation);
276 if (QHwVideoBuffer *buffer = QVideoFramePrivate::hwBuffer(frame))
277 rhi = buffer->associatedCurrentThreadRhi();
284 rhi = qEnsureThreadLocalRhi();
287 if (!rhi || rhi->isRecordingFrame())
288 return convertCPU(frame, transformation);
290 Q_ASSERT(rhi->thread()->isCurrentThread());
294 const QSize frameSize = qRotatedFrameSize(frame.size(), frame.surfaceFormat().rotation());
296 vertexBuffer.reset(rhi->newBuffer(QRhiBuffer::Immutable, QRhiBuffer::VertexBuffer,
sizeof(g_quad)));
297 if (!vertexBuffer->create()) {
298 qCDebug(qLcVideoFrameConverter) <<
"Failed to create vertex buffer. Using CPU conversion.";
299 return convertCPU(frame, transformation);
302 uniformBuffer.reset(rhi->newBuffer(QRhiBuffer::Dynamic, QRhiBuffer::UniformBuffer,
sizeof(QVideoTextureHelper::UniformData)));
303 if (!uniformBuffer->create()) {
304 qCDebug(qLcVideoFrameConverter) <<
"Failed to create uniform buffer. Using CPU conversion.";
305 return convertCPU(frame, transformation);
308 textureSampler.reset(rhi->newSampler(QRhiSampler::Linear, QRhiSampler::Linear, QRhiSampler::None,
309 QRhiSampler::ClampToEdge, QRhiSampler::ClampToEdge));
310 if (!textureSampler->create()) {
311 qCDebug(qLcVideoFrameConverter)
312 <<
"Failed to create texture sampler. Using CPU conversion.";
313 return convertCPU(frame, transformation);
316 shaderResourceBindings.reset(rhi->newShaderResourceBindings());
318 targetTexture.reset(rhi->newTexture(QRhiTexture::RGBA8, frameSize, 1, QRhiTexture::RenderTarget));
319 if (!targetTexture->create()) {
320 qCDebug(qLcVideoFrameConverter) <<
"Failed to create target texture. Using CPU conversion.";
321 return convertCPU(frame, transformation);
324 renderTarget.reset(rhi->newTextureRenderTarget({ { targetTexture.get() } }));
325 renderPass.reset(renderTarget->newCompatibleRenderPassDescriptor());
326 renderTarget->setRenderPassDescriptor(renderPass.get());
327 if (!renderTarget->create()) {
328 qCDebug(qLcVideoFrameConverter) <<
"Failed to create render target. Using CPU conversion.";
329 return convertCPU(frame, transformation);
332 QRhiCommandBuffer *cb =
nullptr;
333 QRhi::FrameOpResult r = rhi->beginOffscreenFrame(&cb);
334 if (r != QRhi::FrameOpSuccess) {
335 qCDebug(qLcVideoFrameConverter) <<
"Failed to set up offscreen frame. Using CPU conversion.";
336 return convertCPU(frame, transformation);
339 QRhiResourceUpdateBatch *rub = rhi->nextResourceUpdateBatch();
342 rub->uploadStaticBuffer(vertexBuffer.get(),
g_quad);
344 QVideoFrame frameTmp = frame;
345 QVideoFrameTexturesUPtr texturesTmp;
346 auto videoFrameTextures = QVideoTextureHelper::createTextures(frameTmp, *rhi, *rub, texturesTmp);
347 if (!videoFrameTextures) {
348 qCDebug(qLcVideoFrameConverter) <<
"Failed obtain textures. Using CPU conversion.";
349 return convertCPU(frame, transformation);
352 if (!updateTextures(rhi, uniformBuffer, textureSampler, shaderResourceBindings,
353 graphicsPipeline, renderPass, frameTmp, videoFrameTextures)) {
354 qCDebug(qLcVideoFrameConverter) <<
"Failed to update textures. Using CPU conversion.";
355 return convertCPU(frame, transformation);
358 float xScale = transformation.mirroredHorizontallyAfterRotation ? -1.0 : 1.0;
361 if (rhi->isYUpInFramebuffer())
364 QMatrix4x4 transform;
365 transform.scale(xScale, yScale);
367 QByteArray uniformData(
sizeof(QVideoTextureHelper::UniformData), Qt::Uninitialized);
368 QVideoTextureHelper::updateUniformData(&uniformData, rhi, frame.surfaceFormat(), frame,
370 rub->updateDynamicBuffer(uniformBuffer.get(), 0, uniformData.size(), uniformData.constData());
372 cb->beginPass(renderTarget.get(), Qt::black, { 1.0f, 0 }, rub);
373 cb->setGraphicsPipeline(graphicsPipeline.get());
375 cb->setViewport({ 0, 0,
float(frameSize.width()),
float(frameSize.height()) });
376 cb->setShaderResources(shaderResourceBindings.get());
378 const quint32 vertexOffset = quint32(
sizeof(
float)) * 16 * transformation.rotationIndex();
379 const QRhiCommandBuffer::VertexInput vbufBinding(vertexBuffer.get(), vertexOffset);
380 cb->setVertexInput(0, 1, &vbufBinding);
384 QRhiReadbackResult readResult;
385 bool readCompleted =
false;
387 readResult.completed = [&readCompleted] { readCompleted =
true; };
389 rub = rhi->nextResourceUpdateBatch();
390 rub->readBackTexture(readDesc, &readResult);
394 rhi->endOffscreenFrame();
396 if (!readCompleted) {
397 qCDebug(qLcVideoFrameConverter) <<
"Failed to read back texture. Using CPU conversion.";
398 return convertCPU(frame, transformation);
401 QByteArray *imageData =
new QByteArray(readResult.data);
403 return QImage(
reinterpret_cast<
const uchar *>(imageData->constData()),
404 readResult.pixelSize.width(), readResult.pixelSize.height(),