drawAtlas can not work in graphite dawn backend

39 views
Skip to first unread message

Ge汁菌

unread,
Jul 29, 2025, 9:06:16 AMJul 29
to skia-discuss
Error code:
/**
 * @file main.cpp
 * @brief Minimal Reproducible Example (MRE) for Skia Bug Report.
 *
 * @purpose Demonstrates that SkCanvas::drawAtlas appears to have no effect
 * when using the Graphite-Dawn backend.
 *
 * @expected_behavior The output file "draw_atlas_mre_output.png" should show a
 * 1920x1080 dark blue canvas with a 100x100 magenta square
 * (containing a yellow circle) drawn at position (50, 50).
 *
 * @actual_behavior The output file shows only the dark blue canvas, indicating
 * the drawAtlas call was ignored or failed silently.
 */

#include <iostream>
#include <vector>
#include <memory>
#include <fstream>
#include <string>

// --- Dawn headers ---
#include <dawn/native/DawnNative.h>
#include <webgpu/webgpu_cpp.h>
#include <dawn/dawn_proc.h>

// --- Skia headers ---
#include <include/core/SkCanvas.h>
#include <include/core/SkPaint.h>
#include <include/core/SkRect.h>
#include <include/core/SkSurface.h>
#include <include/core/SkColor.h>
#include <include/core/SkImage.h>
#include <include/core/SkData.h>
#include <include/core/SkRSXform.h>
#include <include/gpu/graphite/Context.h>
#include <include/gpu/graphite/Recorder.h>
#include <include/gpu/graphite/Surface.h>
#include <include/gpu/graphite/dawn/DawnBackendContext.h>
#include <include/encode/SkPngEncoder.h>
#include <include/core/SkPixmap.h>
#include <include/gpu/graphite/Image.h>
#include <include/gpu/graphite/ContextOptions.h>

// --- Constants ---
constexpr int RENDER_WIDTH = 1920;
constexpr int RENDER_HEIGHT = 1080;
constexpr const char* OUTPUT_FILENAME = "draw_atlas_mre_output.png";

/// <summary>
/// Context for storing async pixel readback results
/// </summary>
struct ReadbackContext
{
    SkImageInfo imageInfo;
    sk_sp<SkData> pngData = nullptr;
};

// Async pixel readback callback
void readback_callback(void* context, std::unique_ptr<const SkImage::AsyncReadResult> result) {
    if (!result || result->count() == 0) return;
    auto* readbackContext = static_cast<ReadbackContext*>(context);
    SkPixmap pixmap(readbackContext->imageInfo, result->data(0), result->rowBytes(0));
    SkDynamicMemoryWStream stream;
    if (SkPngEncoder::Encode(&stream, pixmap, {})) {
        readbackContext->pngData = stream.detachAsData();
    }
}

// File write helper
void write_data_to_file(const sk_sp<SkData>& data, const char* filename) {
    if (!data) {
        std::cerr << "Error: Failed to write file, data is null." << std::endl;
        return;
    }
    std::ofstream out(filename, std::ios::binary);
    out.write(static_cast<const char*>(data->data()), data->size());
    std::cout << "✅ Successfully wrote " << data->size() << " bytes to " << filename << std::endl;
}

/// <summary>
/// Programmatically create a test image to avoid external file dependencies.
/// </summary>
/// <param name="recorder">Skia Graphite Recorder</param>
/// <returns>A GPU texture image with content</returns>
sk_sp<SkImage> CreateProceduralImage(skgpu::graphite::Recorder* recorder) {
    const SkImageInfo info = SkImageInfo::MakeN32Premul(100, 100);
    auto cpuSurface = SkSurfaces::Raster(info);
    SkCanvas* canvas = cpuSurface->getCanvas();

    // Draw a magenta square
    SkPaint bgPaint;
    bgPaint.setColor(SK_ColorMAGENTA);
    canvas->drawRect(SkRect::MakeWH(100, 100), bgPaint);

    // Draw a yellow circle in the center
    SkPaint circlePaint;
    circlePaint.setColor(SK_ColorYELLOW);
    circlePaint.setAntiAlias(true);
    canvas->drawCircle(50, 50, 30, circlePaint);
   
    // Get image from CPU surface and upload to GPU
    sk_sp<SkImage> cpuImage = cpuSurface->makeImageSnapshot();
    std::cout << "✅ Successfully created a 100x100 procedural CPU sprite image." << std::endl;
   
    return SkImages::TextureFromImage(recorder, cpuImage.get());
}

int main(int, char**) {
    // --- 1. Initialize Dawn ---
    // Get native procs for Dawn
    dawnProcSetProcs(&dawn::native::GetProcs());
    auto dawnInstance = std::make_unique<dawn::native::Instance>();

    // Request a D3D12 adapter
    wgpu::RequestAdapterOptions adapterOptions;
    adapterOptions.backendType = wgpu::BackendType::D3D12;
    std::vector<dawn::native::Adapter> adapters = dawnInstance->EnumerateAdapters(&adapterOptions);
    if (adapters.empty()) {
        std::cerr << "Error: Failed to find compatible D3D12 Dawn adapter." << std::endl;
        return -1;
    }
    wgpu::Adapter adapter = adapters[0].Get();
    auto dawnDevice = adapter.CreateDevice();
    std::cout << "✅ Dawn initialized successfully (D3D12 Backend)." << std::endl;

    // --- 2. Initialize Skia Graphite ---
    skgpu::graphite::DawnBackendContext backendContext;
    backendContext.fInstance = wgpu::Instance(dawnInstance->Get());
    backendContext.fDevice = dawnDevice;
    backendContext.fQueue = dawnDevice.GetQueue();
    auto graphiteContext = skgpu::graphite::ContextFactory::MakeDawn(backendContext, {});
    if (!graphiteContext) {
        std::cerr << "Error: Failed to create Skia Graphite Context." << std::endl;
        return -1;
    }
    std::cout << "✅ Skia Graphite initialized successfully." << std::endl;

    // --- 3. Create rendering resources ---
    auto graphiteRecorder = graphiteContext->makeRecorder();
   
    // Create procedural sprite image and upload to GPU
    sk_sp<SkImage> spriteImage = CreateProceduralImage(graphiteRecorder.get());
    if (!spriteImage) {
        std::cerr << "Error: Failed to create procedural sprite image." << std::endl;
        return -1;
    }

    // Create offscreen render target
    const SkImageInfo renderTargetInfo = SkImageInfo::Make(RENDER_WIDTH, RENDER_HEIGHT, kRGBA_8888_SkColorType, kPremul_SkAlphaType);
    auto offscreenSurface = SkSurfaces::RenderTarget(graphiteRecorder.get(), renderTargetInfo);
    if (!offscreenSurface) {
        std::cerr << "Error: Failed to create offscreen render surface." << std::endl;
        return -1;
    }
   
    // --- 4. Core test logic: Execute drawAtlas ---
    SkCanvas* canvas = offscreenSurface->getCanvas();
    canvas->clear(SkColorSetARGB(0xFF, 10, 20, 40)); // Dark blue background

    // Prepare data for drawAtlas
    const SkRect spriteSrcRect = SkRect::Make(spriteImage->bounds());
   
    // We want to draw one sprite
    // Transform: No rotation (ssin=0), scale=1.0 (scos=1), translate to (50, 50)
    const std::vector<SkRSXform> xforms = { SkRSXform::Make(1.0f, 0.0f, 50.0f, 50.0f) };
    const std::vector<SkRect> texRects = { spriteSrcRect };
   
    std::cout << "ℹ️ Calling SkCanvas::drawAtlas..." << std::endl;

    // ***************************************************************
    // * This is the core of the report: The failing call            *
    // ***************************************************************
    canvas->drawAtlas(spriteImage.get(),
                      SkSpan(xforms),
                      SkSpan(texRects),
                      SkSpan<const SkColor>(), // No color blending
                      SkBlendMode::kSrcOver,
                      SkSamplingOptions(SkFilterMode::kNearest),
                      nullptr, // No cull rect
                      nullptr  // No paint
    );

    // --- 5. Submit commands and save result to file ---
    auto recording = graphiteRecorder->snap();
    if (recording) {
        skgpu::graphite::InsertRecordingInfo info = { recording.get() };
        graphiteContext->insertRecording(info);
    }
   
    ReadbackContext context;
    context.imageInfo = renderTargetInfo;
    graphiteContext->asyncRescaleAndReadPixels(offscreenSurface.get(), renderTargetInfo, renderTargetInfo.bounds(),
                                               SkImage::RescaleGamma::kSrc, SkImage::RescaleMode::kNearest,
                                               readback_callback, &context);

    // Submit all pending GPU work and wait for completion
    graphiteContext->submit(skgpu::graphite::SyncToCpu::kYes);
   
    if (context.pngData) {
        write_data_to_file(context.pngData, OUTPUT_FILENAME);
    } else {
        std::cerr << "Error: Async pixel readback failed, no image data received." << std::endl;
        return -1;
    }
   
    std::cout << "\n✅ MRE execution complete. Please check output file '" << OUTPUT_FILENAME << "'." << std::endl;
    return 0;
}

Description:
When using the Graphite-Dawn backend (specifically with D3D12), SkCanvas::drawAtlas appears to have no effect. The call fails silently without rendering anything to the canvas.

Expected Behavior:
The output should show a 1920x1080 dark blue canvas with a 100x100 magenta square (containing a yellow circle) drawn at position (50, 50).

Actual Behavior:
The output shows only the dark blue background, indicating the drawAtlas call had no effect.

Steps to Reproduce:

  1. Build Skia with the provided GN args

  2. Compile and run the attached MRE

  3. Examine the output PNG file

Description:
When using the Graphite-Dawn backend (specifically with D3D12), SkCanvas::drawAtlas appears to have no effect. The call fails silently without rendering anything to the canvas.

Expected Behavior:
The output should show a 1920x1080 dark blue canvas with a 100x100 magenta square (containing a yellow circle) drawn at position (50, 50).

Actual Behavior:
The output shows only the dark blue background, indicating the drawAtlas call had no effect.

Steps to Reproduce:

  1. Build Skia with the provided GN args

  2. Compile and run the attached MRE

  3. Examine the output PNG file

Build Configuration:
cc = "clang"
cxx = "clang++"
clang_win = "$ClangPath"
win_vc = "$VCPath"
win_sdk = "$SDKPath"
win_sdk_version = "$SDKVersion"
is_trivial_abi=false
is_official_build = true
is_debug = false
skia_use_freetype = true
skia_use_harfbuzz = true
skia_pdf_subset_harfbuzz = true
skia_enable_skottie = true
skia_enable_svg = true
skia_enable_ganesh = true
skia_enable_graphite = true
skia_use_dawn = true
dawn_enable_d3d11 = true
dawn_enable_d3d12 = true
dawn_enable_vulkan = true
skia_use_system_expat = false
skia_use_system_icu = false
skia_use_system_libjpeg_turbo = false
skia_use_system_libpng = false
skia_use_system_libwebp = false
skia_use_system_zlib = false
skia_use_system_freetype2 = false
skia_use_system_harfbuzz = false
target_os = "win"
target_cpu = "x64"
extra_cflags_c = [ "/MT" ]
extra_cflags_cc = [ "/MT", "-D_USE_MATH_DEFINES", "/std:c++20" ]
extra_ldflags = [ "/OPT:ICF", "/OPT:REF" ]

Additional Information:

  • Tested on Windows 11 with latest Skia main branch

  • Behavior is consistent across multiple runs

  • Other drawing operations work correctly with the same Graphite-Dawn setup

  • The same code works as expected when using Ganesh backend

Michael Ludwig

unread,
Aug 3, 2025, 12:53:38 PMAug 3
to skia-discuss
We still have to port drawAtlas and drawImageLattice to Graphite
Reply all
Reply to author
Forward
0 new messages