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:
Build Skia with the provided GN args
Compile and run the attached MRE
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:
Build Skia with the provided GN args
Compile and run the attached MRE
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=falseis_official_build = trueis_debug = falseskia_use_freetype = trueskia_use_harfbuzz = trueskia_pdf_subset_harfbuzz = trueskia_enable_skottie = trueskia_enable_svg = trueskia_enable_ganesh = trueskia_enable_graphite = trueskia_use_dawn = truedawn_enable_d3d11 = truedawn_enable_d3d12 = truedawn_enable_vulkan = trueskia_use_system_expat = falseskia_use_system_icu = falseskia_use_system_libjpeg_turbo = falseskia_use_system_libpng = falseskia_use_system_libwebp = falseskia_use_system_zlib = falseskia_use_system_freetype2 = falseskia_use_system_harfbuzz = falsetarget_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