Graphite render to wrapped texture not working

88 views
Skip to first unread message

chola

unread,
Jul 15, 2025, 11:05:03 AM7/15/25
to skia-discuss
Hello,
I have been trying to use skia to render into an existing d3d12 texture for integration into Unreal engine, but I faced a wall. Out of despair, I tried reproducing the issue in a simple directx sample. I didn't manage to get a render to texture to work using graphite over dawn, but the functionaly identical code worked using ganesh with the native d3d12 backend. 
For the following sample, I reproduced the issue only using a rendertarget and droping the shared texture requirement. Both repros are based on the HelloTexture sample provided by microsoft for directx 12 (https://learn.microsoft.com/en-us/samples/microsoft/directx-graphics-samples/d3d12-hello-world-samples-win32/)
Here is the ganesh code : 
  • initialization:
GrD3DBackendContext contextDesc;
contextDesc.fAdapter.reset(adapter);
contextDesc.fDevice.reset(m_device.Get());
contextDesc.fQueue.reset(m_commandQueue.Get());
contextDesc.fMemoryAllocator = nullptr;

Context->SkiaContext = GrDirectContext::MakeDirect3D(contextDesc);

SkISize planeDimensions = SkISize::Make(1024, 1024);
SkImageInfo infos = SkImageInfo::Make(SkISize::Make(1024, 1024), SkColorInfo(SkColorType::kBGRA_8888_SkColorType, SkAlphaType::kOpaque_SkAlphaType, SkColorSpace::MakeSRGB()));
SkSurfaceProps props(0, kUnknown_SkPixelGeometry);
sk_sp<SkSurface> gpuSurface = SkSurfaces::RenderTarget(Context->SkiaContext.get(), skgpu::Budgeted::kYes, infos);

if (!gpuSurface)
{
    //UE_LOG(SkiaIntegration, Error, TEXT("Failed to wrap backend"));
    return;
}

Context->Surface = gpuSurface;
  • render
SkCanvas* gpuCanvas = Context->Surface->getCanvas();
draw(gpuCanvas);
Context->Surface->asyncRescaleAndReadPixels(Context->Surface->imageInfo(), SkIRect::MakeSize(Context->Surface->imageInfo().dimensions()), SkImage::RescaleGamma::kSrc, SkImage::RescaleMode::kNearest, [](SkSurface::ReadPixelsContext, std::unique_ptr<const SkSurface::AsyncReadResult> result) {
    const uint8_t* pixData = reinterpret_cast<const uint8_t*>(result->data(0));

    }, this);
Context->SkiaContext->flushAndSubmit(GrSyncCpu::kYes);
Context->SkiaContext->checkAsyncWorkCompletion();

The readback of the pixels show that the rendertarget is properly filled after a few iterations. However, when I implemented render to an existing texture , for some reason, I need to call  asyncRescaleAndReadPixels after the draw call otherwise the texture is not updated. Any pointer on that? I tried pushing a backendfence to the queue to no avail.

Here is the graphite+dawn code, for which I didn't manage to get a working render : 
  • initialization
dawnProcSetProcs(&dawn::native::GetProcs());
Context = new webgpuContext();

std::vector<const char*> enableToggleNames;
enableToggleNames.reserve(1);
std::vector<const char*> disabledToggleNames;
disabledToggleNames.reserve(1);
wgpu::DawnTogglesDescriptor toggles;
toggles.enabledToggles = enableToggleNames.data();
toggles.enabledToggleCount = enableToggleNames.size();
toggles.disabledToggles = disabledToggleNames.data();
toggles.disabledToggleCount = disabledToggleNames.size();

wgpu::InstanceDescriptor instanceDescriptor{};
instanceDescriptor.nextInChain = &toggles;
instanceDescriptor.capabilities.timedWaitAnyEnable = true;
Context->Instance = wgpu::CreateInstance(&instanceDescriptor);
if (Context->Instance == nullptr) {
    //UE_LOG(SkiaIntegration, Error, TEXT("dawn webgpu Instance creation failed!"));
    return;
}
// Synchronously request the adapter.
wgpu::RequestAdapterOptions reqAdapterOptions = {};
//reqAdapterOptions.backendType = wgpu::BackendType::D3D12;
reqAdapterOptions.backendType = wgpu::BackendType::D3D12;


auto callback = [](wgpu::RequestAdapterStatus status, wgpu::Adapter adapter, const char* message, void* userdata) {
    if (status != wgpu::RequestAdapterStatus::Success) {
        //UE_LOG(SkiaIntegration, Error, TEXT("dawn webgpu Failed to get an adapter:%s"), UTF8_TO_TCHAR(message));
        return;
    }
    *static_cast<wgpu::Adapter*>(userdata) = adapter;
    };


auto callbackMode = wgpu::CallbackMode::WaitAnyOnly;
void* userdata = &Context->Adapter;
Context->Instance.WaitAny(Context->Instance.RequestAdapter(&reqAdapterOptions, callbackMode, callback, userdata), UINT64_MAX);
if (Context->Adapter == nullptr) {
    //UE_LOG(SkiaIntegration, Error, TEXT("dawn webgpu RequestAdapter failed!"));
    return;
}


wgpu::DawnAdapterPropertiesPowerPreference power_props{};

wgpu::AdapterInfo info{};
info.nextInChain = &power_props;

Context->Adapter.GetInfo(&info);

std::vector<wgpu::FeatureName> requiredFeatures = {
wgpu::FeatureName::SharedTextureMemoryDXGISharedHandle,
wgpu::FeatureName::SharedFenceDXGISharedHandle,
wgpu::FeatureName::DawnInternalUsages,
wgpu::FeatureName::FlexibleTextureViews,
wgpu::FeatureName::Snorm16TextureFormats,
wgpu::FeatureName::Unorm16TextureFormats
};
wgpu::DeviceDescriptor deviceDescriptor;
deviceDescriptor.requiredFeatures = requiredFeatures.data();
deviceDescriptor.requiredFeatureCount = requiredFeatures.size();
deviceDescriptor.SetUncapturedErrorCallback([](const wgpu::Device&, wgpu::ErrorType type, wgpu::StringView message) {
    //UE_LOG(SkiaIntegration, Error, TEXT("dawn error : %s"), UTF8_TO_TCHAR(message.data));
    });

Context->Device = Context->Adapter.CreateDevice(&deviceDescriptor);

Context->Device.SetLoggingCallback([](wgpu::LoggingType type, wgpu::StringView message) {
    //UE_LOG(SkiaIntegration, Error, TEXT("wgpu error : %s"), UTF8_TO_TCHAR(message.data));
    });

using namespace skgpu::graphite;
DawnBackendContext contextDesc;
contextDesc.fInstance = Context->Instance;
contextDesc.fDevice = Context->Device;
contextDesc.fQueue = Context->Device.GetQueue();
ContextOptions options;

Context->SkiaContext = ContextFactory::MakeDawn(contextDesc, options);

Context->Recorder = Context->SkiaContext->makeRecorder();

SkISize planeDimensions = SkISize::Make(1024, 1024);
SkImageInfo infos = SkImageInfo::Make(SkISize::Make(1024, 1024), SkColorInfo(SkColorType::kBGRA_8888_SkColorType, SkAlphaType::kOpaque_SkAlphaType, SkColorSpace::MakeSRGB()));
SkSurfaceProps props(0, kUnknown_SkPixelGeometry);
sk_sp<SkSurface> gpuSurface = SkSurfaces::RenderTarget(Context->Recorder.get(), infos, skgpu::Mipmapped::kNo, &props, "skia_rt");

if (!gpuSurface)
{
    //UE_LOG(SkiaIntegration, Error, TEXT("Failed to wrap backend"));
    return;
}

Context->Surface = gpuSurface;

  • render:
SkCanvas* gpuCanvas = Context->Surface->getCanvas();
draw(gpuCanvas);

Context->Instance.ProcessEvents();
Context->SkiaContext->submit(skgpu::graphite::SyncToCpu::kYes);

Context->Instance.ProcessEvents();

Context->SkiaContext->asyncRescaleAndReadPixels(Context->Surface.get(), Context->Surface->imageInfo(), SkIRect::MakeSize(Context->Surface->imageInfo().dimensions()), SkImage::RescaleGamma::kSrc, SkImage::RescaleMode::kNearest, [](SkSurface::ReadPixelsContext, std::unique_ptr<const SkSurface::AsyncReadResult> result) {
    const uint8_t* pixData = reinterpret_cast<const uint8_t*>(result->data(0));

    }, this);

Context->SkiaContext->submit(skgpu::graphite::SyncToCpu::kYes);
Context->SkiaContext->checkAsyncWorkCompletion();

Any idea why the graphite version (which I would rather use) won't update render to the provided rendertarget (the readback of the pixels is always black) ?

I can provide the complete integration into the sample if required.
Reply all
Reply to author
Forward
0 new messages