Skia使用Dawn后端在Windows窗口中绘图
首先创建一个Windows窗口,如下代码所示:
void WindowMain::createWindow()
{
static bool isWcexReg = false;
static const TCHAR clsName[] = L"SkiaApp";
static WNDCLASSEX wcex;
auto hinstance = GetModuleHandle(NULL);
if (!isWcexReg) {
wcex.cbSize = sizeof(WNDCLASSEX);
wcex.style = CS_HREDRAW | CS_VREDRAW | CS_OWNDC;
wcex.lpfnWndProc = &WindowMain::wndProc;
wcex.cbClsExtra = 0;
wcex.cbWndExtra = 0;
wcex.hInstance = hinstance;
wcex.hIcon = LoadIcon(hinstance, IDI_APPLICATION);
wcex.hCursor = LoadCursor(nullptr, IDC_ARROW);
wcex.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
wcex.lpszMenuName = nullptr;
wcex.lpszClassName = clsName;
wcex.hIconSm = LoadIcon(hinstance, IDI_APPLICATION);
if (!RegisterClassEx(&wcex)) {
return;
}
isWcexReg = true;
}
hwnd = CreateWindowEx(NULL, clsName, clsName, WS_OVERLAPPEDWINDOW,
x, y, w, h, nullptr, nullptr, hinstance, nullptr);
SetWindowLongPtr(hwnd, GWLP_USERDATA, (LONG_PTR)this);
}
接着创建Dawn实例:
void WindowMain::initDawnInstance()
{
WGPUInstanceDescriptor desc{};
desc.features.timedWaitAnyEnable = true;
dawnInstance = std::make_unique<dawn::native::Instance>(&desc);
}
然后创建Dawn设备:
void WindowMain::initDawnDevice()
{
DawnProcTable backendProcs = dawn::native::GetProcs();
dawnProcSetProcs(&backendProcs);
static constexpr const char* kToggles[] = {
"allow_unsafe_apis",
"use_user_defined_labels_in_backend",
"disable_robustness", //禁用这玩意儿,提升性能
"use_tint_ir",
};
wgpu::DawnTogglesDescriptor togglesDesc;
togglesDesc.enabledToggleCount = std::size(kToggles) - 1;
togglesDesc.enabledToggles = kToggles;
wgpu::RequestAdapterOptions adapterOptions;
adapterOptions.backendType = wgpu::BackendType::D3D11;
adapterOptions.featureLevel = wgpu::FeatureLevel::Core;
adapterOptions.nextInChain = &togglesDesc;
std::vector<dawn::native::Adapter> adapters = dawnInstance->EnumerateAdapters(&adapterOptions);
if (adapters.empty()) {
return;
}
wgpu::Adapter adapter = adapters[0].Get();
std::vector<wgpu::FeatureName> features;
if (adapter.HasFeature(wgpu::FeatureName::MSAARenderToSingleSampled)) {
features.push_back(wgpu::FeatureName::MSAARenderToSingleSampled);
}
if (adapter.HasFeature(wgpu::FeatureName::TransientAttachments)) {
features.push_back(wgpu::FeatureName::TransientAttachments);
}
if (adapter.HasFeature(wgpu::FeatureName::Unorm16TextureFormats)) {
features.push_back(wgpu::FeatureName::Unorm16TextureFormats);
}
if (adapter.HasFeature(wgpu::FeatureName::DualSourceBlending)) {
features.push_back(wgpu::FeatureName::DualSourceBlending);
}
if (adapter.HasFeature(wgpu::FeatureName::FramebufferFetch)) {
features.push_back(wgpu::FeatureName::FramebufferFetch);
}
if (adapter.HasFeature(wgpu::FeatureName::BufferMapExtendedUsages)) {
features.push_back(wgpu::FeatureName::BufferMapExtendedUsages);
}
if (adapter.HasFeature(wgpu::FeatureName::TextureCompressionETC2)) {
features.push_back(wgpu::FeatureName::TextureCompressionETC2);
}
if (adapter.HasFeature(wgpu::FeatureName::TextureCompressionBC)) {
features.push_back(wgpu::FeatureName::TextureCompressionBC);
}
if (adapter.HasFeature(wgpu::FeatureName::R8UnormStorage)) {
features.push_back(wgpu::FeatureName::R8UnormStorage);
}
if (adapter.HasFeature(wgpu::FeatureName::DawnLoadResolveTexture)) {
features.push_back(wgpu::FeatureName::DawnLoadResolveTexture);
}
if (adapter.HasFeature(wgpu::FeatureName::DawnPartialLoadResolveTexture)) {
features.push_back(wgpu::FeatureName::DawnPartialLoadResolveTexture);
}
wgpu::DeviceDescriptor deviceDescriptor;
deviceDescriptor.requiredFeatures = features.data();
deviceDescriptor.requiredFeatureCount = features.size();
deviceDescriptor.nextInChain = &togglesDesc;
deviceDescriptor.SetDeviceLostCallback(
wgpu::CallbackMode::AllowSpontaneous,
[](const wgpu::Device&, wgpu::DeviceLostReason reason, const char* message) {
if (reason != wgpu::DeviceLostReason::Destroyed &&
reason != wgpu::DeviceLostReason::InstanceDropped) {
SK_ABORT("Device lost: %s\n", message);
}
});
deviceDescriptor.SetUncapturedErrorCallback(
[](const wgpu::Device&, wgpu::ErrorType, const char* message) {
SkDebugf("Device error: %s\n", message);
SkASSERT(false);
});
dawnDevice = adapter.CreateDevice(&deviceDescriptor);
}
然后创建Dawn表面(注意,dawnSurface不是Skia的SkSurface)
void WindowMain::initDawnSurface()
{
wgpu::SurfaceDescriptorFromWindowsHWND surfaceChainedDesc;
surfaceChainedDesc.hwnd = hwnd;
surfaceChainedDesc.hinstance = GetModuleHandle(nullptr);
wgpu::SurfaceDescriptor surfaceDesc;
surfaceDesc.nextInChain = &surfaceChainedDesc;
dawnSurface = wgpu::Instance(dawnInstance->Get()).CreateSurface(&surfaceDesc);
}
然后创建绘图上下文和Recorder
void WindowMain::initGraphite()
{
skgpu::graphite::DawnBackendContext backendContext;
backendContext.fInstance = wgpu::Instance(dawnInstance->Get());
backendContext.fDevice = dawnDevice;
backendContext.fQueue = dawnDevice.GetQueue();
skgpu::graphite::ContextOptions fContextOptions;
graphiteContext = skgpu::graphite::ContextFactory::MakeDawn(backendContext, fContextOptions);
if (!graphiteContext) {
SkASSERT(false);
return;
}
graphiteRecorder = graphiteContext->makeRecorder();
}
然后配置Dawn表面(改变窗口大小时,也得调用这个方法)
void WindowMain::configSurface()
{
wgpu::SurfaceConfiguration config;
config.device = dawnDevice;
config.format = surfaceFormat;
config.usage = wgpu::TextureUsage::RenderAttachment | wgpu::TextureUsage::TextureBinding |
wgpu::TextureUsage::CopySrc | wgpu::TextureUsage::CopyDst;
config.width = w;
config.height = h;
//wgpu::PresentMode::Immediate 立即渲染会撕裂
//wgpu::PresentMode::Fifo 渲染的帧内容会进入一个 FIFO(先进先出)队列,等待显示器的垂直同步信号(VSync)后再显示到屏幕上。
config.presentMode = wgpu::PresentMode::Fifo;
dawnSurface.Configure(&config);
}
在窗口中绘图:
case WM_PAINT:{
PAINTSTRUCT ps;
BeginPaint(hWnd, &ps);
auto surface = win->getSurface();
win->paint(surface->getCanvas());
win->flush();
EndPaint(hWnd, &ps);
return 0;
}
每次绘图都要重新获取Skia的Surface
sk_sp<SkSurface> WindowMain::getSurface()
{
wgpu::SurfaceTexture surfaceTexture;
dawnSurface.GetCurrentTexture(&surfaceTexture);
auto texture = surfaceTexture.texture;
skgpu::graphite::DawnTextureInfo info(1,skgpu::Mipmapped::kNo,
surfaceFormat, wgpu::TextureUsage::None, wgpu::TextureAspect::All);
auto backendTex = skgpu::graphite::BackendTextures::MakeDawn(texture.Get());
auto surface = SkSurfaces::WrapBackendTexture(graphiteRecorder.get(),backendTex,
kBGRA_8888_SkColorType, displayParams.fColorSpace, &displayParams.fSurfaceProps);
return surface;
}
下面是绘图逻辑:
void WindowMain::paint(SkCanvas* canvas)
{
canvas->clear(0xFFFFFFFF);
SkPaint paint;
paint.setColor(SK_ColorRED);
SkRect rect = SkRect::MakeXYWH(w - 150, h - 150, 140, 140);
canvas->drawRect(rect, paint);
}
最后把绘图内容同步到窗口中
void WindowMain::flush()
{
std::unique_ptr<skgpu::graphite::Recording> recording = graphiteRecorder->snap();
if (recording) {
skgpu::graphite::InsertRecordingInfo info;
info.fRecording = recording.get();
graphiteContext->insertRecording(info);
graphiteContext->submit(skgpu::graphite::SyncToCpu::kNo);
}
dawnSurface.Present();
}
这个示例代码有一个小问题,就是改变窗口大小时,窗口画面更新不流畅,如下图所示:
目前我还没找到好得解决办法,希望懂的老师不吝赐教,必有重谢。