103 std::unique_ptr<QRhiBuffer> &uniformBuffer,
104 std::unique_ptr<QRhiSampler> &textureSampler,
105 std::unique_ptr<QRhiShaderResourceBindings> &shaderResourceBindings,
106 std::unique_ptr<QRhiGraphicsPipeline> &graphicsPipeline,
107 std::unique_ptr<QRhiRenderPassDescriptor> &renderPass,
109 const QVideoFrameTexturesUPtr &videoFrameTextures)
111 auto format = frame.surfaceFormat();
112 auto pixelFormat = format.pixelFormat();
114 auto textureDesc = QVideoTextureHelper::textureDescription(pixelFormat);
118 *b++ = QRhiShaderResourceBinding::uniformBuffer(0, QRhiShaderResourceBinding::VertexStage | QRhiShaderResourceBinding::FragmentStage,
119 uniformBuffer.get());
120 for (
int i = 0; i < textureDesc->nplanes; ++i)
121 *b++ = QRhiShaderResourceBinding::sampledTexture(i + 1, QRhiShaderResourceBinding::FragmentStage,
122 videoFrameTextures->texture(i), textureSampler.get());
123 shaderResourceBindings->setBindings(bindings, b);
124 shaderResourceBindings->create();
126 graphicsPipeline.reset(rhi->newGraphicsPipeline());
127 graphicsPipeline->setTopology(QRhiGraphicsPipeline::TriangleStrip);
129 QShader vs = ensureShader(QVideoTextureHelper::vertexShaderFileName(format));
133 QShader fs = ensureShader(QVideoTextureHelper::fragmentShaderFileName(format, rhi));
137 graphicsPipeline->setShaderStages({
138 { QRhiShaderStage::Vertex, vs },
139 { QRhiShaderStage::Fragment, fs }
143 inputLayout.setBindings({
144 { 4 *
sizeof(
float) }
146 inputLayout.setAttributes({
147 { 0, 0, QRhiVertexInputAttribute::Float2, 0 },
148 { 0, 1, QRhiVertexInputAttribute::Float2, 2 *
sizeof(
float) }
151 graphicsPipeline->setVertexInputLayout(inputLayout);
152 graphicsPipeline->setShaderResourceBindings(shaderResourceBindings.get());
153 graphicsPipeline->setRenderPassDescriptor(renderPass.get());
154 graphicsPipeline->create();
161 QVideoFrame varFrame = frame;
162 if (!varFrame.map(QVideoFrame::ReadOnly)) {
163 qCDebug(qLcVideoFrameConverter) << Q_FUNC_INFO <<
": frame mapping failed";
167 auto unmap =
std::optional(QScopeGuard([&] {
171 QSpan<uchar> jpegData{
173 varFrame.mappedBytes(0),
176 constexpr std::array<uchar, 2> soiMarker{ uchar(0xff), uchar(0xd8) };
177 if (!QtMultimediaPrivate::ranges::equal(jpegData.first(2), soiMarker, std::equal_to<
void>{})) {
178 qCDebug(qLcVideoFrameConverter)
179 << Q_FUNC_INFO <<
": JPEG data does not start with SOI marker";
183 constexpr std::array<uchar, 2> eoiMarker{ uchar(0xff), uchar(0xd9) };
187 if (!QtMultimediaPrivate::ranges::equal(jpegData.last(2), eoiMarker, std::equal_to<
void>{})) {
188 qCDebug(qLcVideoFrameConverter)
189 << Q_FUNC_INFO <<
": JPEG data does not end with EOI marker";
191 auto eoi_it = std::find_end(jpegData.begin(), jpegData.end(), std::begin(eoiMarker),
192 std::end(eoiMarker));
193 if (eoi_it == jpegData.end()) {
194 qCWarning(qLcVideoFrameConverter)
195 << Q_FUNC_INFO <<
": JPEG data does not contain EOI marker";
199 const size_t newSize =
std::distance(jpegData.begin(), eoi_it) +
std::size(eoiMarker);
200 jpegData = jpegData.first(newSize);
203 QImage image = QImage::fromData(jpegData,
"JPG");
204 unmap =
std::nullopt;
205 rasterTransform(image, transform);
241 QMacAutoReleasePool releasePool;
244 std::unique_ptr<QRhiRenderPassDescriptor> renderPass;
245 std::unique_ptr<QRhiBuffer> vertexBuffer;
246 std::unique_ptr<QRhiBuffer> uniformBuffer;
247 std::unique_ptr<QRhiTexture> targetTexture;
248 std::unique_ptr<QRhiTextureRenderTarget> renderTarget;
249 std::unique_ptr<QRhiSampler> textureSampler;
250 std::unique_ptr<QRhiShaderResourceBindings> shaderResourceBindings;
251 std::unique_ptr<QRhiGraphicsPipeline> graphicsPipeline;
253 if (frame.size().isEmpty() || frame.pixelFormat() == QVideoFrameFormat::Format_Invalid)
256 if (frame.pixelFormat() == QVideoFrameFormat::Format_Jpeg)
257 return convertJPEG(frame, transformation);
260 return convertCPU(frame, transformation);
264 if (QHwVideoBuffer *buffer = QVideoFramePrivate::hwBuffer(frame))
267 if (!rhi || !rhi->thread()->isCurrentThread())
268 rhi = qEnsureThreadLocalRhi(rhi);
270 if (!rhi || rhi->isRecordingFrame())
271 return convertCPU(frame, transformation);
275 const QSize frameSize = qRotatedFrameSize(frame.size(), frame.surfaceFormat().rotation());
277 vertexBuffer.reset(rhi->newBuffer(QRhiBuffer::Immutable, QRhiBuffer::VertexBuffer,
sizeof(g_quad)));
278 vertexBuffer->create();
280 uniformBuffer.reset(rhi->newBuffer(QRhiBuffer::Dynamic, QRhiBuffer::UniformBuffer,
sizeof(QVideoTextureHelper::UniformData)));
281 uniformBuffer->create();
283 textureSampler.reset(rhi->newSampler(QRhiSampler::Linear, QRhiSampler::Linear, QRhiSampler::None,
284 QRhiSampler::ClampToEdge, QRhiSampler::ClampToEdge));
285 textureSampler->create();
287 shaderResourceBindings.reset(rhi->newShaderResourceBindings());
289 targetTexture.reset(rhi->newTexture(QRhiTexture::RGBA8, frameSize, 1, QRhiTexture::RenderTarget));
290 if (!targetTexture->create()) {
291 qCDebug(qLcVideoFrameConverter) <<
"Failed to create target texture. Using CPU conversion.";
292 return convertCPU(frame, transformation);
295 renderTarget.reset(rhi->newTextureRenderTarget({ { targetTexture.get() } }));
296 renderPass.reset(renderTarget->newCompatibleRenderPassDescriptor());
297 renderTarget->setRenderPassDescriptor(renderPass.get());
298 renderTarget->create();
300 QRhiCommandBuffer *cb =
nullptr;
301 QRhi::FrameOpResult r = rhi->beginOffscreenFrame(&cb);
302 if (r != QRhi::FrameOpSuccess) {
303 qCDebug(qLcVideoFrameConverter) <<
"Failed to set up offscreen frame. Using CPU conversion.";
304 return convertCPU(frame, transformation);
307 QRhiResourceUpdateBatch *rub = rhi->nextResourceUpdateBatch();
310 rub->uploadStaticBuffer(vertexBuffer.get(), g_quad);
312 QVideoFrame frameTmp = frame;
313 QVideoFrameTexturesUPtr texturesTmp;
314 auto videoFrameTextures = QVideoTextureHelper::createTextures(frameTmp, *rhi, *rub, texturesTmp);
315 if (!videoFrameTextures) {
316 qCDebug(qLcVideoFrameConverter) <<
"Failed obtain textures. Using CPU conversion.";
317 return convertCPU(frame, transformation);
320 if (!updateTextures(rhi, uniformBuffer, textureSampler, shaderResourceBindings,
321 graphicsPipeline, renderPass, frameTmp, videoFrameTextures)) {
322 qCDebug(qLcVideoFrameConverter) <<
"Failed to update textures. Using CPU conversion.";
323 return convertCPU(frame, transformation);
326 float xScale = transformation.mirroredHorizontallyAfterRotation ? -1.0 : 1.0;
329 if (rhi->isYUpInFramebuffer())
332 QMatrix4x4 transform;
333 transform.scale(xScale, yScale);
335 QByteArray uniformData(
sizeof(QVideoTextureHelper::UniformData), Qt::Uninitialized);
336 QVideoTextureHelper::updateUniformData(&uniformData, rhi, frame.surfaceFormat(), frame,
338 rub->updateDynamicBuffer(uniformBuffer.get(), 0, uniformData.size(), uniformData.constData());
340 cb->beginPass(renderTarget.get(), Qt::black, { 1.0f, 0 }, rub);
341 cb->setGraphicsPipeline(graphicsPipeline.get());
343 cb->setViewport({ 0, 0,
float(frameSize.width()),
float(frameSize.height()) });
344 cb->setShaderResources(shaderResourceBindings.get());
346 const quint32 vertexOffset = quint32(
sizeof(
float)) * 16 * transformation.rotationIndex();
347 const QRhiCommandBuffer::VertexInput vbufBinding(vertexBuffer.get(), vertexOffset);
348 cb->setVertexInput(0, 1, &vbufBinding);
352 QRhiReadbackResult readResult;
353 bool readCompleted =
false;
355 readResult.completed = [&readCompleted] { readCompleted =
true; };
357 rub = rhi->nextResourceUpdateBatch();
358 rub->readBackTexture(readDesc, &readResult);
362 rhi->endOffscreenFrame();
364 if (!readCompleted) {
365 qCDebug(qLcVideoFrameConverter) <<
"Failed to read back texture. Using CPU conversion.";
366 return convertCPU(frame, transformation);
369 QByteArray *imageData =
new QByteArray(readResult.data);
371 return QImage(
reinterpret_cast<
const uchar *>(imageData->constData()),
372 readResult.pixelSize.width(), readResult.pixelSize.height(),
373 QImage::Format_RGBA8888_Premultiplied, imageCleanupHandler, imageData);