Revision control
++ // Copyright (C) 2025 The Qt Company Ltd.
++ // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
++
++ #include "qwindowsd3d11testdevicecontext_p.h"
++
++ #include <QtMultimediaTestLib/private/qvideoframetestutils_p.h>
++
++ #include <QtGui/rhi/qrhi.h>
++
++ #include <QtCore/private/qsystemerror_p.h>
++
++ #include <algorithm>
++ #include <cstring>
++
++ namespace {
++
++ // Subresource index of the first mip level of 'arraySlice' in 'texture'.
++ UINT sliceSubresource(const ComPtr<ID3D11Texture2D> &texture, UINT arraySlice)
++ {
++ D3D11_TEXTURE2D_DESC desc{};
++ texture->GetDesc(&desc);
++ return D3D11CalcSubresource(0, arraySlice, desc.MipLevels);
++ }
++
++ } // namespace
++
++ ComResult<QColor>
++ QWindowsD3D11TestDeviceContext::getFirstPixelColor(const ComPtr<ID3D11Texture2D> &texture) const
++ {
++ // Get the texture description
++ D3D11_TEXTURE2D_DESC desc{};
++ texture->GetDesc(&desc);
++
++ // Create a staging texture to map the data
++ D3D11_TEXTURE2D_DESC stagingDesc = desc;
++ stagingDesc.Usage = D3D11_USAGE_STAGING;
++ stagingDesc.BindFlags = 0;
++ stagingDesc.CPUAccessFlags = D3D11_CPU_ACCESS_READ;
++ stagingDesc.MiscFlags = 0;
++
++ ComPtr<ID3D11Texture2D> stagingTexture;
++ HRESULT hr = device->CreateTexture2D(&stagingDesc, nullptr, &stagingTexture);
++ if (FAILED(hr)) {
++ qWarning() << "Failed to create staging texture for first pixel read"
++ << QSystemError::windowsComString(hr);
++ return q23::unexpected{ hr };
++ }
++
++ // Copy the texture data to the staging texture
++ context->CopyResource(stagingTexture.Get(), texture.Get());
++
++ // Map the staging texture to access its data
++ D3D11_MAPPED_SUBRESOURCE mappedResource{};
++ hr = context->Map(stagingTexture.Get(), 0, D3D11_MAP_READ, 0, &mappedResource);
++ if (FAILED(hr)) {
++ qWarning() << "Failed to map staging texture for first pixel read"
++ << QSystemError::windowsComString(hr);
++ return q23::unexpected{ hr };
++ }
++
++ // Get the value of the first pixel (top-left corner)
++ unsigned char *data = static_cast<unsigned char *>(mappedResource.pData);
++
++ QColor firstPixel{ data[0], data[1], data[2], data[3] };
++
++ // Unmap the staging texture
++ context->Unmap(stagingTexture.Get(), 0);
++
++ return firstPixel;
++ }
++
++ ComResult<QColor>
++ QWindowsD3D11TestDeviceContext::getFirstPixelColorBGRA(const ComPtr<ID3D11Texture2D> &texture) const
++ {
++ D3D11_TEXTURE2D_DESC desc{};
++ texture->GetDesc(&desc);
++
++ D3D11_TEXTURE2D_DESC stagingDesc = desc;
++ stagingDesc.Usage = D3D11_USAGE_STAGING;
++ stagingDesc.BindFlags = 0;
++ stagingDesc.CPUAccessFlags = D3D11_CPU_ACCESS_READ;
++ stagingDesc.MiscFlags = 0;
++
++ ComPtr<ID3D11Texture2D> stagingTexture;
++ HRESULT hr = device->CreateTexture2D(&stagingDesc, nullptr, &stagingTexture);
++ if (hr != S_OK)
++ return q23::unexpected{ hr };
++
++ context->CopyResource(stagingTexture.Get(), texture.Get());
++
++ D3D11_MAPPED_SUBRESOURCE mappedResource{};
++ hr = context->Map(stagingTexture.Get(), 0, D3D11_MAP_READ, 0, &mappedResource);
++ if (hr != S_OK)
++ return q23::unexpected{ hr };
++
++ unsigned char *data = static_cast<unsigned char *>(mappedResource.pData);
++
++ QColor firstPixel{ data[2], data[1], data[0], data[3] };
++
++ context->Unmap(stagingTexture.Get(), 0);
++
++ return firstPixel;
++ }
++
++ ComResult<ComPtr<ID3D11Texture2D>>
++ QWindowsD3D11TestDeviceContext::createTextureArray(QSize size,
++ const std::vector<QColor> &colors) const
++ {
++ D3D11_TEXTURE2D_DESC texDesc{};
++ texDesc.Width = size.width();
++ texDesc.Height = size.height();
++ texDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM;
++ texDesc.Usage = D3D11_USAGE_DEFAULT;
++ texDesc.CPUAccessFlags = 0;
++ texDesc.MiscFlags = 0;
++ texDesc.BindFlags = 0;
++ texDesc.ArraySize = static_cast<UINT>(colors.size());
++ texDesc.MipLevels = 1;
++ texDesc.SampleDesc = { 1, 0 };
++
++ ComPtr<ID3D11Texture2D> tex;
++ HRESULT hr = device->CreateTexture2D(&texDesc, nullptr, tex.GetAddressOf());
++
++ if (FAILED(hr)) {
++ qWarning() << "Failed to create texture array" << QSystemError::windowsComString(hr);
++ return q23::unexpected{ hr };
++ }
++
++ hr = fillTextureWithColors(tex, colors);
++ if (FAILED(hr)) {
++ qWarning() << "Failed to fill texture array" << QSystemError::windowsComString(hr);
++ return q23::unexpected{ hr };
++ }
++
++ return tex;
++ }
++
++ bool QWindowsD3D11TestDeviceContext::isTextureFormatSupported(DXGI_FORMAT format) const
++ {
++ if (!device)
++ return false;
++
++ UINT formatSupport = 0;
++ const HRESULT hr = device->CheckFormatSupport(format, &formatSupport);
++ if (FAILED(hr))
++ return false;
++
++ return (formatSupport & D3D11_FORMAT_SUPPORT_TEXTURE2D) != 0;
++ }
++
++ ComResult<ComPtr<ID3D11Texture2D>>
++ QWindowsD3D11TestDeviceContext::createNV12Texture(QSize size) const
++ {
++ D3D11_TEXTURE2D_DESC texDesc{};
++ texDesc.Width = size.width();
++ texDesc.Height = size.height();
++ texDesc.Format = DXGI_FORMAT_NV12;
++ texDesc.Usage = D3D11_USAGE_DEFAULT;
++ texDesc.CPUAccessFlags = 0;
++ texDesc.MiscFlags = 0;
++ texDesc.BindFlags = 0;
++ texDesc.ArraySize = 1;
++ texDesc.MipLevels = 1;
++ texDesc.SampleDesc = { 1, 0 };
++
++ ComPtr<ID3D11Texture2D> tex;
++ HRESULT hr = device->CreateTexture2D(&texDesc, nullptr, tex.GetAddressOf());
++ if (FAILED(hr)) {
++ qWarning() << "Failed to create NV12 texture" << QSystemError::windowsComString(hr);
++ return q23::unexpected{ hr };
++ }
++
++ return tex;
++ }
++
++ HRESULT QWindowsD3D11TestDeviceContext::fillNV12Texture(const ComPtr<ID3D11Texture2D> &texture,
++ QSize size, quint8 lumaValue,
++ quint8 chromaUValue, quint8 chromaVValue,
++ UINT arraySlice) const
++ {
++ const ComResult<ComPtr<ID3D11Texture2D>> staging =
++ createStagingSliceCopy(texture, arraySlice, D3D11_CPU_ACCESS_WRITE);
++ if (!staging) {
++ qWarning() << "Failed to create staging texture for NV12 fill"
++ << QSystemError::windowsComString(staging.error());
++ return staging.error();
++ }
++ const ComPtr<ID3D11Texture2D> &stagingTexture = *staging;
++
++ D3D11_MAPPED_SUBRESOURCE mappedResource{};
++ HRESULT hr = context->Map(stagingTexture.Get(), 0, D3D11_MAP_WRITE, 0, &mappedResource);
++ if (FAILED(hr)) {
++ qWarning() << "Failed to map staging texture for NV12 fill"
++ << QSystemError::windowsComString(hr);
++ return hr;
++ }
++
++ unsigned char *data = static_cast<unsigned char *>(mappedResource.pData);
++ const int width = size.width();
++ const int height = size.height();
++
++ // Luma plane: one byte per pixel.
++ for (int row = 0; row < height; ++row) {
++ unsigned char *rowData = data + row * mappedResource.RowPitch;
++ std::fill(rowData, rowData + width, lumaValue);
++ }
++
++ // Chroma plane: interleaved U/V, half the width and height of the luma plane, laid out
++ // immediately below the luma plane's rows in the same subresource.
++ unsigned char *chromaBase = data + height * mappedResource.RowPitch;
++ for (int row = 0; row < height / 2; ++row) {
++ unsigned char *rowData = chromaBase + row * mappedResource.RowPitch;
++ for (int col = 0; col < width / 2; ++col) {
++ rowData[2 * col] = chromaUValue;
++ rowData[2 * col + 1] = chromaVValue;
++ }
++ }
++
++ context->Unmap(stagingTexture.Get(), 0);
++ context->CopySubresourceRegion(texture.Get(), sliceSubresource(texture, arraySlice), 0, 0, 0,
++ stagingTexture.Get(), 0, nullptr);
++
++ return S_OK;
++ }
++
++ ComResult<ComPtr<ID3D11Texture2D>>
++ QWindowsD3D11TestDeviceContext::createFilledNV12Texture(QSize size, quint8 lumaValue,
++ quint8 chromaU, quint8 chromaV) const
++ {
++ const ComResult<ComPtr<ID3D11Texture2D>> tex = createNV12Texture(size);
++ if (!tex)
++ return tex;
++
++ const HRESULT hr = fillNV12Texture(*tex, size, lumaValue, chromaU, chromaV);
++ if (hr != S_OK)
++ return q23::unexpected{ hr };
++
++ return tex;
++ }
++
++ ComResult<ComPtr<ID3D11Texture2D>>
++ QWindowsD3D11TestDeviceContext::createP010Texture(QSize size) const
++ {
++ D3D11_TEXTURE2D_DESC texDesc{};
++ texDesc.Width = size.width();
++ texDesc.Height = size.height();
++ texDesc.Format = DXGI_FORMAT_P010;
++ texDesc.Usage = D3D11_USAGE_DEFAULT;
++ texDesc.CPUAccessFlags = 0;
++ texDesc.MiscFlags = 0;
++ texDesc.BindFlags = 0;
++ texDesc.ArraySize = 1;
++ texDesc.MipLevels = 1;
++ texDesc.SampleDesc = { 1, 0 };
++
++ ComPtr<ID3D11Texture2D> tex;
++ HRESULT hr = device->CreateTexture2D(&texDesc, nullptr, tex.GetAddressOf());
++ if (FAILED(hr)) {
++ qWarning() << "Failed to create P010 texture" << QSystemError::windowsComString(hr);
++ return q23::unexpected{ hr };
++ }
++
++ return tex;
++ }
++
++ HRESULT QWindowsD3D11TestDeviceContext::fillP010Texture(const ComPtr<ID3D11Texture2D> &texture,
++ QSize size, quint16 lumaValue,
++ quint16 chromaUValue, quint16 chromaVValue,
++ UINT arraySlice) const
++ {
++ const ComResult<ComPtr<ID3D11Texture2D>> staging =
++ createStagingSliceCopy(texture, arraySlice, D3D11_CPU_ACCESS_WRITE);
++ if (!staging) {
++ qWarning() << "Failed to create staging texture for P010 fill"
++ << QSystemError::windowsComString(staging.error());
++ return staging.error();
++ }
++ const ComPtr<ID3D11Texture2D> &stagingTexture = *staging;
++
++ D3D11_MAPPED_SUBRESOURCE mappedResource{};
++ HRESULT hr = context->Map(stagingTexture.Get(), 0, D3D11_MAP_WRITE, 0, &mappedResource);
++ if (FAILED(hr)) {
++ qWarning() << "Failed to map staging texture for P010 fill"
++ << QSystemError::windowsComString(hr);
++ return hr;
++ }
++
++ unsigned char *data = static_cast<unsigned char *>(mappedResource.pData);
++
++ // P010 stores each 10-bit sample left-shifted into the high bits of a 16-bit little-endian
++ // word.
++ const quint16 shiftedLuma = quint16(lumaValue << 6);
++ const char lumaTexel[2] = { char(shiftedLuma & 0xff), char(shiftedLuma >> 8) };
++ QtMultimediaTest::fillPlaneRows(data, mappedResource.RowPitch, size.width(), size.height(),
++ QByteArrayView(lumaTexel, sizeof(lumaTexel)));
++
++ const quint16 shiftedU = quint16(chromaUValue << 6);
++ const quint16 shiftedV = quint16(chromaVValue << 6);
++ const char chromaTexel[4] = {
++ char(shiftedU & 0xff),
++ char(shiftedU >> 8),
++ char(shiftedV & 0xff),
++ char(shiftedV >> 8),
++ };
++ unsigned char *chromaBase = data + size.height() * mappedResource.RowPitch;
++ QtMultimediaTest::fillPlaneRows(chromaBase, mappedResource.RowPitch, size.width() / 2,
++ size.height() / 2,
++ QByteArrayView(chromaTexel, sizeof(chromaTexel)));
++
++ context->Unmap(stagingTexture.Get(), 0);
++ context->CopySubresourceRegion(texture.Get(), sliceSubresource(texture, arraySlice), 0, 0, 0,
++ stagingTexture.Get(), 0, nullptr);
++
++ return S_OK;
++ }
++
++ ComResult<ComPtr<ID3D11Texture2D>>
++ QWindowsD3D11TestDeviceContext::createSinglePlaneTexture(QSize size, DXGI_FORMAT format) const
++ {
++ D3D11_TEXTURE2D_DESC texDesc{};
++ texDesc.Width = size.width();
++ texDesc.Height = size.height();
++ texDesc.Format = format;
++ texDesc.Usage = D3D11_USAGE_DEFAULT;
++ texDesc.CPUAccessFlags = 0;
++ texDesc.MiscFlags = 0;
++ texDesc.BindFlags = 0;
++ texDesc.ArraySize = 1;
++ texDesc.MipLevels = 1;
++ texDesc.SampleDesc = { 1, 0 };
++
++ ComPtr<ID3D11Texture2D> tex;
++ HRESULT hr = device->CreateTexture2D(&texDesc, nullptr, tex.GetAddressOf());
++ if (FAILED(hr)) {
++ qWarning() << "Failed to create single-plane texture" << QSystemError::windowsComString(hr);
++ return q23::unexpected{ hr };
++ }
++
++ return tex;
++ }
++
++ HRESULT
++ QWindowsD3D11TestDeviceContext::fillSinglePlaneTexture(const ComPtr<ID3D11Texture2D> &texture,
++ QSize size, QByteArrayView texel,
++ UINT arraySlice) const
++ {
++ const ComResult<ComPtr<ID3D11Texture2D>> staging =
++ createStagingSliceCopy(texture, arraySlice, D3D11_CPU_ACCESS_WRITE);
++ if (!staging) {
++ qWarning() << "Failed to create staging texture for single-plane fill"
++ << QSystemError::windowsComString(staging.error());
++ return staging.error();
++ }
++ const ComPtr<ID3D11Texture2D> &stagingTexture = *staging;
++
++ D3D11_MAPPED_SUBRESOURCE mappedResource{};
++ HRESULT hr = context->Map(stagingTexture.Get(), 0, D3D11_MAP_WRITE, 0, &mappedResource);
++ if (FAILED(hr)) {
++ qWarning() << "Failed to map staging texture for single-plane fill"
++ << QSystemError::windowsComString(hr);
++ return hr;
++ }
++
++ unsigned char *data = static_cast<unsigned char *>(mappedResource.pData);
++ QtMultimediaTest::fillPlaneRows(data, mappedResource.RowPitch, size.width(), size.height(),
++ texel);
++
++ context->Unmap(stagingTexture.Get(), 0);
++ context->CopySubresourceRegion(texture.Get(), sliceSubresource(texture, arraySlice), 0, 0, 0,
++ stagingTexture.Get(), 0, nullptr);
++
++ return S_OK;
++ }
++
++ ComResult<QByteArray>
++ QWindowsD3D11TestDeviceContext::readPlaneRows(const ComPtr<ID3D11Texture2D> &texture, int rowOffset,
++ int width, int height, int texelSize,
++ UINT arraySlice) const
++ {
++ const ComResult<ComPtr<ID3D11Texture2D>> staging =
++ createStagingSliceCopy(texture, arraySlice, D3D11_CPU_ACCESS_READ);
++ if (!staging) {
++ qWarning() << "Failed to create staging texture for plane read"
++ << QSystemError::windowsComString(staging.error());
++ return q23::unexpected{ staging.error() };
++ }
++ const ComPtr<ID3D11Texture2D> &stagingTexture = *staging;
++
++ D3D11_MAPPED_SUBRESOURCE mappedResource{};
++ HRESULT hr = context->Map(stagingTexture.Get(), 0, D3D11_MAP_READ, 0, &mappedResource);
++ if (FAILED(hr)) {
++ qWarning() << "Failed to map staging texture for plane read"
++ << QSystemError::windowsComString(hr);
++ return q23::unexpected{ hr };
++ }
++
++ const auto *base = static_cast<const unsigned char *>(mappedResource.pData);
++ const qsizetype rowBytes = qsizetype(width) * texelSize;
++ QByteArray result(rowBytes * height, Qt::Uninitialized);
++ for (int row = 0; row < height; ++row) {
++ const unsigned char *rowData = base + (rowOffset + row) * mappedResource.RowPitch;
++ std::copy(rowData, rowData + rowBytes, result.data() + row * rowBytes);
++ }
++
++ context->Unmap(stagingTexture.Get(), 0);
++
++ return result;
++ }
++
++ HRESULT
++ QWindowsD3D11TestDeviceContext::fillTextureWithColors(const ComPtr<ID3D11Texture2D> &texture,
++ const std::vector<QColor> &colors) const
++ {
++ // Get the texture description
++ D3D11_TEXTURE2D_DESC desc{};
++ texture->GetDesc(&desc);
++
++ // Create a staging texture to map the data
++ D3D11_TEXTURE2D_DESC stagingDesc = desc;
++ stagingDesc.Usage = D3D11_USAGE_STAGING;
++ stagingDesc.BindFlags = 0;
++ stagingDesc.CPUAccessFlags = D3D11_CPU_ACCESS_WRITE;
++ stagingDesc.MiscFlags = 0;
++
++ ComPtr<ID3D11Texture2D> stagingTexture;
++ HRESULT hr = device->CreateTexture2D(&stagingDesc, nullptr, stagingTexture.GetAddressOf());
++
++ if (FAILED(hr)) {
++ qWarning() << "Failed to create staging texture for color fill"
++ << QSystemError::windowsComString(hr);
++ return hr;
++ }
++
++ // Map the staging texture to access its data
++ D3D11_MAPPED_SUBRESOURCE mappedResource{};
++ context->CopyResource(stagingTexture.Get(), texture.Get());
++ hr = context->Map(stagingTexture.Get(), 0, D3D11_MAP_WRITE, 0, &mappedResource);
++ if (FAILED(hr)) {
++ qWarning() << "Failed to map staging texture for color fill"
++ << QSystemError::windowsComString(hr);
++ return hr;
++ }
++
++ unsigned char *data = static_cast<unsigned char *>(mappedResource.pData);
++ for (UINT plane = 0; plane < desc.ArraySize; ++plane) {
++ const QColor color = colors[plane];
++ for (UINT row = 0; row < desc.Height; ++row) {
++ for (UINT col = 0; col < desc.Width; ++col) {
++ unsigned char *pixel = data + row * mappedResource.RowPitch
++ + plane * mappedResource.DepthPitch + col * 4;
++ pixel[0] = static_cast<unsigned char>(color.red());
++ pixel[1] = static_cast<unsigned char>(color.green());
++ pixel[2] = static_cast<unsigned char>(color.blue());
++ pixel[3] = static_cast<unsigned char>(color.alpha());
++ }
++ }
++ }
++
++ // Unmap the staging texture and copy it back to the original texture
++ context->Unmap(stagingTexture.Get(), 0);
++ context->CopyResource(texture.Get(), stagingTexture.Get());
++
++ return S_OK;
++ }
++
++ ComResult<ComPtr<ID3D11Texture2D>>
++ QWindowsD3D11TestDeviceContext::createStagingSliceCopy(const ComPtr<ID3D11Texture2D> &texture,
++ UINT arraySlice, UINT cpuAccessFlags) const
++ {
++ D3D11_TEXTURE2D_DESC stagingDesc{};
++ texture->GetDesc(&stagingDesc);
++ stagingDesc.ArraySize = 1;
++ stagingDesc.MipLevels = 1;
++ stagingDesc.Usage = D3D11_USAGE_STAGING;
++ stagingDesc.BindFlags = 0;
++ stagingDesc.CPUAccessFlags = cpuAccessFlags;
++ stagingDesc.MiscFlags = 0;
++
++ ComPtr<ID3D11Texture2D> stagingTexture;
++ if (const HRESULT hr = device->CreateTexture2D(&stagingDesc, nullptr, &stagingTexture);
++ FAILED(hr)) {
++ return q23::unexpected{ hr };
++ }
++
++ context->CopySubresourceRegion(stagingTexture.Get(), 0, 0, 0, 0, texture.Get(),
++ sliceSubresource(texture, arraySlice), nullptr);
++ return stagingTexture;
++ }
++
++ static ComResult<QWindowsD3D11TestDeviceContext>
++ createD3D11TestDeviceContextImpl(D3D_DRIVER_TYPE driverType, UINT flags)
++ {
++ QWindowsD3D11TestDeviceContext devContext;
++ ComPtr<ID3D11Device> srcDev;
++ HRESULT hr =
++ D3D11CreateDevice(nullptr, driverType, nullptr, flags, nullptr, 0, D3D11_SDK_VERSION,
++ srcDev.GetAddressOf(), nullptr, devContext.context.GetAddressOf());
++ if (FAILED(hr)) {
++ qWarning() << "Failed to create D3D11 device" << QSystemError::windowsComString(hr);
++ return q23::unexpected{ hr };
++ }
++
++ hr = srcDev.As(&devContext.device);
++ if (FAILED(hr)) {
++ qWarning() << "Failed to query ID3D11Device1" << QSystemError::windowsComString(hr);
++ return q23::unexpected{ hr };
++ }
++
++ return devContext;
++ }
++
++ ComResult<QWindowsD3D11TestDeviceContext> createD3D11TestDeviceContext()
++ {
++ // VIDEO_SUPPORT is required for the device to expose ID3D11VideoDevice/ID3D11VideoContext,
++ // which FFmpeg's D3D11VA hwdevice queries for on init (see d3d11va_device_init in
++ // hwcontext_d3d11va.c).
++ return createD3D11TestDeviceContextImpl(D3D_DRIVER_TYPE_HARDWARE,
++ D3D11_CREATE_DEVICE_DISABLE_GPU_TIMEOUT
++ | D3D11_CREATE_DEVICE_VIDEO_SUPPORT);
++ }
++
++ ComResult<QWindowsD3D11TestDeviceContext> createWarpD3D11TestDeviceContext()
++ {
++ // WARP's software rasterizer doesn't implement ID3D11VideoDevice/ID3D11VideoContext at all:
++ // requesting VIDEO_SUPPORT here makes D3D11CreateDevice fail outright with
++ // DXGI_ERROR_UNSUPPORTED. Tests using a WARP device only exercise the "different adapter /
++ // CpuDevice" rejection paths, which don't need it.
++ return createD3D11TestDeviceContextImpl(D3D_DRIVER_TYPE_WARP, 0);
++ }
++
++ ComResult<QWindowsD3D11TestDeviceContext> wrapRhiD3D11Device(QRhi &rhi)
++ {
++ const auto *native = static_cast<const QRhiD3D11NativeHandles *>(rhi.nativeHandles());
++ if (!native || !native->dev) {
++ qWarning() << "Failed to wrap RHI D3D11 device: invalid native handles";
++ return q23::unexpected{ E_FAIL };
++ }
++
++ QWindowsD3D11TestDeviceContext ctx;
++ const ComPtr<ID3D11Device> device = static_cast<ID3D11Device *>(native->dev);
++ HRESULT hr = device.As(&ctx.device);
++ if (FAILED(hr)) {
++ qWarning() << "Failed to query ID3D11Device1 from RHI device"
++ << QSystemError::windowsComString(hr);
++ return q23::unexpected{ hr };
++ }
++
++ ctx.device->GetImmediateContext(ctx.context.GetAddressOf());
++ if (!ctx.context) {
++ qWarning() << "Failed to get immediate context from RHI device";
++ return q23::unexpected{ E_FAIL };
++ }
++
++ return ctx;
++ }