mirror of
https://github.com/mitch030504/Wiicompiled_VR_Frame.git
synced 2026-10-06 07:00:30 +02:00
When no backend could create a window and a WebGPU device, aurora hit an ASSERT and the process aborted with only SDL's last error in the log. Keep the first adapter, device or window error, return it from aurora_initialize with a new initializationStatus, and have the runtime throw it so the existing fatal popup and fatal log record the reason. ImGui shutdown now skips backends that never initialized, so the shutdown that follows a failed start does not crash. From upstream patchzyy/Wiicompiled 6f14bde (#244, KartPad batch). Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Wg7mB8ogCWmp9GH19Uc82B
1094 lines
44 KiB
C++
1094 lines
44 KiB
C++
#include "gpu.hpp"
|
|
|
|
#include <array>
|
|
#include <algorithm>
|
|
#include <atomic>
|
|
#include <cstdint>
|
|
#include <cstdlib>
|
|
#include <cstring>
|
|
#include <mutex>
|
|
#include <string_view>
|
|
#include <utility>
|
|
#include <vector>
|
|
|
|
#include <aurora/aurora.h>
|
|
#include <aurora/gfx.h>
|
|
#include <aurora/render_size_limits.hpp>
|
|
#include <magic_enum.hpp>
|
|
#include <webgpu/webgpu_cpp.h>
|
|
|
|
#include "../android_debug.hpp"
|
|
#include "fdm.hpp"
|
|
#include "../gfx/common.hpp"
|
|
#include "../internal.hpp"
|
|
#include "../window.hpp"
|
|
#include "../dolphin/vi/vi_internal.hpp"
|
|
|
|
#if defined(WEBGPU_DAWN) && !defined(__MINGW32__)
|
|
#include "../dawn/BackendBinding.hpp"
|
|
#include <dawn/native/DawnNative.h>
|
|
#elif defined(WEBGPU_DAWN)
|
|
#include "../dawn/BackendBinding.hpp"
|
|
#endif
|
|
|
|
#if defined(WEBGPU_DAWN) && defined(_WIN32)
|
|
#include <windows.h>
|
|
#endif
|
|
|
|
namespace aurora::gx {
|
|
void clear_display_copy_cache() noexcept;
|
|
} // namespace aurora::gx
|
|
namespace aurora::gfx {
|
|
void clear_offscreen_cache();
|
|
} // namespace aurora::gfx
|
|
|
|
namespace aurora::webgpu {
|
|
static Module Log("aurora::gpu");
|
|
|
|
#if defined(WEBGPU_DAWN) && defined(_WIN32)
|
|
// RequestAdapterOptionsLUID is a Dawn-native extension whose exported C++
|
|
// constructor cannot be called safely by this project's LLVM-MinGW consumer.
|
|
// Its wire representation is deliberately just a WebGPU chained header plus
|
|
// the Win32 LUID, so describe that C ABI directly and let RequestAdapter
|
|
// consume it through the ordinary WebGPU entry point.
|
|
struct RequestAdapterOptionsLuidWire {
|
|
wgpu::ChainedStruct chain{};
|
|
LUID adapterLuid{};
|
|
};
|
|
#endif
|
|
|
|
wgpu::Device g_device;
|
|
wgpu::Queue g_queue;
|
|
wgpu::Surface g_surface;
|
|
wgpu::BackendType g_backendType;
|
|
GraphicsConfig g_graphicsConfig;
|
|
TextureWithSampler g_frameBuffer;
|
|
TextureWithSampler g_frameBufferResolved;
|
|
TextureWithSampler g_depthBuffer;
|
|
|
|
// EFB -> XFB copy pipeline
|
|
static wgpu::BindGroupLayout g_CopyBindGroupLayout;
|
|
wgpu::RenderPipeline g_CopyPipeline;
|
|
wgpu::BindGroup g_CopyBindGroup;
|
|
static bool g_presentSourceOverrideActive = false;
|
|
static wgpu::BindGroup g_presentSourceOverrideBindGroup;
|
|
static wgpu::Texture g_presentSourceOverrideTexture;
|
|
static wgpu::Extent3D g_presentSourceOverrideSize{};
|
|
static wgpu::TextureFormat g_presentSourceOverrideFormat = wgpu::TextureFormat::Undefined;
|
|
|
|
static wgpu::Adapter g_adapter;
|
|
wgpu::Instance g_instance;
|
|
static wgpu::AdapterInfo g_adapterInfo;
|
|
static wgpu::SurfaceCapabilities g_surfaceCapabilities;
|
|
bool g_bcTexturesSupported;
|
|
bool g_timestampQueriesSupported = false;
|
|
// Written by Dawn's device-loss callback and consumed at ordered frame boundaries. Keep the
|
|
// callback free of logging, allocation, teardown and renderer state mutation.
|
|
static std::atomic_bool g_deviceLost{false};
|
|
static std::atomic<wgpu::DeviceLostReason> g_deviceLostReason{wgpu::DeviceLostReason::Unknown};
|
|
// The reason enum is almost always `Unknown`, while Dawn's message carries the real cause, so
|
|
// keep a truncated copy. Written with a plain memcpy, published by the g_deviceLost store.
|
|
static std::array<char, 256> g_deviceLostMessage{};
|
|
// Errors raised before initialize() completes must not be fatal: the backend fallback loop retries
|
|
// the next backend, and a broken ICD can raise uncaptured errors mid-probe.
|
|
static std::atomic_bool g_initialized{false};
|
|
|
|
namespace {
|
|
|
|
struct RenderTargetSize {
|
|
uint32_t width;
|
|
uint32_t height;
|
|
};
|
|
|
|
RenderTargetSize clamp_render_target_size(uint32_t width, uint32_t height) noexcept {
|
|
const uint32_t maxDimension = g_graphicsConfig.maxTextureDimension2D;
|
|
if (width == 0 || height == 0 || maxDimension == 0 || maxDimension == WGPU_LIMIT_U32_UNDEFINED ||
|
|
(width <= maxDimension && height <= maxDimension)) {
|
|
return {width, height};
|
|
}
|
|
|
|
// Keep the requested aspect while fitting both axes inside the adapter's maximum 2D texture size.
|
|
// 64-bit arithmetic so a large window cannot wrap during the scale.
|
|
if (width > maxDimension) {
|
|
height = std::max(1u, static_cast<uint32_t>((static_cast<uint64_t>(height) * maxDimension) / width));
|
|
width = maxDimension;
|
|
}
|
|
if (height > maxDimension) {
|
|
width = std::max(1u, static_cast<uint32_t>((static_cast<uint64_t>(width) * maxDimension) / height));
|
|
height = maxDimension;
|
|
}
|
|
return {width, height};
|
|
}
|
|
|
|
RenderTargetSize clamp_frame_buffer_size(uint32_t width, uint32_t height) noexcept {
|
|
const auto adapterClamped = clamp_render_target_size(width, height);
|
|
const auto budgeted =
|
|
render_size_limits::fit_framebuffer_to_budget(adapterClamped.width, adapterClamped.height,
|
|
g_graphicsConfig.maxTextureDimension2D);
|
|
return {budgeted.width, budgeted.height};
|
|
}
|
|
|
|
// V-Sync is never enabled: the guest drives its own pacing, and blocking in Present() couples the
|
|
// whole machine to the monitor (a 120 FPS target on a 75 Hz display runs in slow motion).
|
|
wgpu::PresentMode best_present_mode() {
|
|
const auto supports = [](const wgpu::PresentMode candidate) {
|
|
for (size_t i = 0; i < g_surfaceCapabilities.presentModeCount; ++i) {
|
|
if (g_surfaceCapabilities.presentModes[i] == candidate) {
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
};
|
|
// Vulkan prefers Mailbox, every other backend Immediate. Under window capture the Vulkan driver
|
|
// cannot flip and Immediate leaks about a megabyte per present until the device is lost.
|
|
const bool preferMailbox = g_backendType == wgpu::BackendType::Vulkan;
|
|
if (preferMailbox && supports(wgpu::PresentMode::Mailbox)) {
|
|
return wgpu::PresentMode::Mailbox;
|
|
}
|
|
if (supports(wgpu::PresentMode::Immediate)) {
|
|
return wgpu::PresentMode::Immediate;
|
|
}
|
|
if (g_backendType != wgpu::BackendType::Metal && supports(wgpu::PresentMode::Mailbox)) {
|
|
return wgpu::PresentMode::Mailbox;
|
|
}
|
|
// Mailbox is preferred over Fifo because Fifo caps presentation at the refresh rate and every slot
|
|
// deadline after the first is missed. Reaching this means neither is offered, so say so loudly.
|
|
Log.warn("Surface supports neither Immediate nor Mailbox; falling back to Fifo. Presentation "
|
|
"is capped at the display refresh rate, so the game may run slower than its own "
|
|
"pace and frame interpolation cannot exceed the refresh rate.");
|
|
return wgpu::PresentMode::Fifo;
|
|
}
|
|
|
|
wgpu::TextureFormat to_linear(wgpu::TextureFormat format) {
|
|
if (format == wgpu::TextureFormat::RGBA8UnormSrgb) {
|
|
return wgpu::TextureFormat::RGBA8Unorm;
|
|
}
|
|
if (format == wgpu::TextureFormat::BGRA8UnormSrgb) {
|
|
return wgpu::TextureFormat::BGRA8Unorm;
|
|
}
|
|
return format;
|
|
}
|
|
|
|
wgpu::TextureFormat best_surface_format() {
|
|
if (g_surfaceCapabilities.formatCount == 0) {
|
|
return wgpu::TextureFormat::Undefined;
|
|
}
|
|
#if defined(__ANDROID__)
|
|
// The OpenXR bridge shares eyes through AHardwareBuffers, and Android has no
|
|
// BGRA AHardwareBuffer format, so an XR interop build must render RGBA8 to
|
|
// keep the eye copy legal. Only the preference order changes; a surface
|
|
// without RGBA8 still falls through to the generic choice below.
|
|
if (g_config.xrInterop) {
|
|
for (size_t i = 0; i < g_surfaceCapabilities.formatCount; ++i) {
|
|
if (to_linear(g_surfaceCapabilities.formats[i]) == wgpu::TextureFormat::RGBA8Unorm) {
|
|
return wgpu::TextureFormat::RGBA8Unorm;
|
|
}
|
|
}
|
|
}
|
|
#endif
|
|
for (size_t i = 0; i < g_surfaceCapabilities.formatCount; ++i) {
|
|
const auto format = to_linear(g_surfaceCapabilities.formats[i]);
|
|
if (format == wgpu::TextureFormat::RGBA8Unorm || format == wgpu::TextureFormat::BGRA8Unorm) {
|
|
return format;
|
|
}
|
|
}
|
|
return g_surfaceCapabilities.formats[0];
|
|
}
|
|
|
|
} // namespace
|
|
|
|
TextureWithSampler create_render_texture(uint32_t width, uint32_t height, bool multisampled) {
|
|
const auto renderTargetSize = clamp_render_target_size(width, height);
|
|
const wgpu::Extent3D size{
|
|
.width = renderTargetSize.width,
|
|
.height = renderTargetSize.height,
|
|
.depthOrArrayLayers = 1,
|
|
};
|
|
const auto format = g_graphicsConfig.surfaceConfiguration.format;
|
|
uint32_t sampleCount = 1;
|
|
if (multisampled) {
|
|
sampleCount = g_graphicsConfig.msaaSamples;
|
|
}
|
|
if (width == 0 || height == 0) {
|
|
Log.fatal("Invalid render texture size! {}x{}, multisampled {}, format {}", width, height, static_cast<uint32_t>(format), multisampled);
|
|
}
|
|
const wgpu::TextureDescriptor textureDescriptor{
|
|
.label = "Render texture",
|
|
.usage = wgpu::TextureUsage::RenderAttachment | wgpu::TextureUsage::TextureBinding | wgpu::TextureUsage::CopySrc |
|
|
wgpu::TextureUsage::CopyDst,
|
|
.dimension = wgpu::TextureDimension::e2D,
|
|
.size = size,
|
|
.format = format,
|
|
.mipLevelCount = 1,
|
|
.sampleCount = sampleCount,
|
|
};
|
|
auto texture = g_device.CreateTexture(&textureDescriptor);
|
|
|
|
constexpr wgpu::TextureViewDescriptor viewDescriptor{
|
|
.label = "Render texture view",
|
|
.dimension = wgpu::TextureViewDimension::e2D,
|
|
};
|
|
auto view = texture.CreateView(&viewDescriptor);
|
|
|
|
constexpr wgpu::SamplerDescriptor samplerDescriptor{
|
|
.label = "Render sampler",
|
|
.addressModeU = wgpu::AddressMode::ClampToEdge,
|
|
.addressModeV = wgpu::AddressMode::ClampToEdge,
|
|
.addressModeW = wgpu::AddressMode::ClampToEdge,
|
|
.magFilter = wgpu::FilterMode::Linear,
|
|
.minFilter = wgpu::FilterMode::Linear,
|
|
.mipmapFilter = wgpu::MipmapFilterMode::Linear,
|
|
.lodMinClamp = 0.f,
|
|
.lodMaxClamp = 1000.f,
|
|
.maxAnisotropy = 1,
|
|
};
|
|
auto sampler = g_device.CreateSampler(&samplerDescriptor);
|
|
|
|
return {
|
|
.texture = std::move(texture),
|
|
.view = std::move(view),
|
|
.size = size,
|
|
.format = format,
|
|
.sampler = std::move(sampler),
|
|
};
|
|
}
|
|
|
|
const TextureWithSampler& present_source() noexcept {
|
|
return g_graphicsConfig.msaaSamples > 1 ? g_frameBufferResolved : g_frameBuffer;
|
|
}
|
|
|
|
PresentSource current_present_source() noexcept {
|
|
if (g_presentSourceOverrideActive && g_presentSourceOverrideBindGroup != nullptr) {
|
|
return {
|
|
.bindGroup = g_presentSourceOverrideBindGroup,
|
|
.texture = g_presentSourceOverrideTexture,
|
|
.size = g_presentSourceOverrideSize,
|
|
.format = g_presentSourceOverrideFormat,
|
|
};
|
|
}
|
|
|
|
return {
|
|
.bindGroup = g_CopyBindGroup,
|
|
.texture = present_source().texture,
|
|
.size = present_source().size,
|
|
.format = present_source().format,
|
|
};
|
|
}
|
|
|
|
void set_present_source_override(wgpu::BindGroup bindGroup, wgpu::Texture texture, wgpu::Extent3D size,
|
|
wgpu::TextureFormat format) noexcept {
|
|
g_presentSourceOverrideBindGroup = std::move(bindGroup);
|
|
g_presentSourceOverrideTexture = std::move(texture);
|
|
g_presentSourceOverrideSize = size;
|
|
g_presentSourceOverrideFormat = format;
|
|
g_presentSourceOverrideActive = true;
|
|
}
|
|
|
|
void clear_present_source_override() noexcept {
|
|
g_presentSourceOverrideActive = false;
|
|
g_presentSourceOverrideBindGroup = {};
|
|
g_presentSourceOverrideTexture = {};
|
|
g_presentSourceOverrideSize = {};
|
|
g_presentSourceOverrideFormat = wgpu::TextureFormat::Undefined;
|
|
}
|
|
|
|
Viewport calculate_present_viewport_for_aspect(uint32_t surface_width, uint32_t surface_height,
|
|
float content_aspect) noexcept {
|
|
if (surface_width == 0 || surface_height == 0 || !(content_aspect > 0.f)) {
|
|
return {};
|
|
}
|
|
|
|
uint32_t viewport_width = surface_width;
|
|
uint32_t viewport_height = std::min<uint32_t>(
|
|
surface_height, std::max<uint32_t>(1u, static_cast<uint32_t>(std::lround(static_cast<double>(viewport_width) *
|
|
static_cast<double>(1.f / content_aspect)))));
|
|
if (viewport_height == surface_height) {
|
|
viewport_width = std::min<uint32_t>(
|
|
surface_width, std::max<uint32_t>(1u, static_cast<uint32_t>(std::lround(static_cast<double>(viewport_height) *
|
|
static_cast<double>(content_aspect)))));
|
|
}
|
|
|
|
return {
|
|
.left = static_cast<float>((surface_width - viewport_width) / 2),
|
|
.top = static_cast<float>((surface_height - viewport_height) / 2),
|
|
.width = static_cast<float>(viewport_width),
|
|
.height = static_cast<float>(viewport_height),
|
|
.znear = 0.f,
|
|
.zfar = 1.f,
|
|
};
|
|
}
|
|
|
|
Viewport calculate_present_viewport(uint32_t surface_width, uint32_t surface_height, uint32_t content_width,
|
|
uint32_t content_height) noexcept {
|
|
if (content_width == 0 || content_height == 0) {
|
|
return {};
|
|
}
|
|
return calculate_present_viewport_for_aspect(
|
|
surface_width, surface_height, static_cast<float>(content_width) / static_cast<float>(content_height));
|
|
}
|
|
|
|
static TextureWithSampler create_depth_texture(uint32_t width, uint32_t height) {
|
|
const auto renderTargetSize = clamp_render_target_size(width, height);
|
|
const wgpu::Extent3D size{
|
|
.width = renderTargetSize.width,
|
|
.height = renderTargetSize.height,
|
|
.depthOrArrayLayers = 1,
|
|
};
|
|
const auto format = g_graphicsConfig.depthFormat;
|
|
const wgpu::TextureDescriptor textureDescriptor{
|
|
.label = "Depth texture",
|
|
.usage = wgpu::TextureUsage::RenderAttachment | wgpu::TextureUsage::TextureBinding,
|
|
.dimension = wgpu::TextureDimension::e2D,
|
|
.size = size,
|
|
.format = format,
|
|
.mipLevelCount = 1,
|
|
.sampleCount = g_graphicsConfig.msaaSamples,
|
|
};
|
|
auto texture = g_device.CreateTexture(&textureDescriptor);
|
|
|
|
const wgpu::TextureViewDescriptor viewDescriptor{
|
|
.label = "Depth texture view",
|
|
.dimension = wgpu::TextureViewDimension::e2D,
|
|
};
|
|
auto view = texture.CreateView(&viewDescriptor);
|
|
|
|
const wgpu::SamplerDescriptor samplerDescriptor{
|
|
.label = "Depth sampler",
|
|
.addressModeU = wgpu::AddressMode::ClampToEdge,
|
|
.addressModeV = wgpu::AddressMode::ClampToEdge,
|
|
.addressModeW = wgpu::AddressMode::ClampToEdge,
|
|
.magFilter = wgpu::FilterMode::Linear,
|
|
.minFilter = wgpu::FilterMode::Linear,
|
|
.mipmapFilter = wgpu::MipmapFilterMode::Linear,
|
|
.lodMinClamp = 0.f,
|
|
.lodMaxClamp = 1000.f,
|
|
.maxAnisotropy = 1,
|
|
};
|
|
auto sampler = g_device.CreateSampler(&samplerDescriptor);
|
|
|
|
return {
|
|
.texture = std::move(texture),
|
|
.view = std::move(view),
|
|
.size = size,
|
|
.format = format,
|
|
.sampler = std::move(sampler),
|
|
};
|
|
}
|
|
|
|
void create_copy_pipeline() {
|
|
wgpu::ShaderSourceWGSL sourceDescriptor{};
|
|
sourceDescriptor.code = R"""(
|
|
@group(0) @binding(0)
|
|
var efb_sampler: sampler;
|
|
@group(0) @binding(1)
|
|
var efb_texture: texture_2d<f32>;
|
|
|
|
struct VertexOutput {
|
|
@builtin(position) pos: vec4<f32>,
|
|
@location(0) uv: vec2<f32>,
|
|
};
|
|
|
|
var<private> pos: array<vec2<f32>, 3> = array<vec2<f32>, 3>(
|
|
vec2(-1.0, 1.0),
|
|
vec2(-1.0, -3.0),
|
|
vec2(3.0, 1.0),
|
|
);
|
|
var<private> uvs: array<vec2<f32>, 3> = array<vec2<f32>, 3>(
|
|
vec2(0.0, 0.0),
|
|
vec2(0.0, 2.0),
|
|
vec2(2.0, 0.0),
|
|
);
|
|
|
|
@vertex
|
|
fn vs_main(@builtin(vertex_index) vtxIdx: u32) -> VertexOutput {
|
|
var out: VertexOutput;
|
|
out.pos = vec4<f32>(pos[vtxIdx], 0.0, 1.0);
|
|
out.uv = uvs[vtxIdx];
|
|
return out;
|
|
}
|
|
|
|
@fragment
|
|
fn fs_main(in: VertexOutput) -> @location(0) vec4<f32> {
|
|
let color = textureSample(efb_texture, efb_sampler, in.uv);
|
|
return vec4(color.rgb, 1.0);
|
|
}
|
|
)""";
|
|
const wgpu::ShaderModuleDescriptor moduleDescriptor{
|
|
.nextInChain = &sourceDescriptor,
|
|
.label = "XFB Copy Module",
|
|
};
|
|
auto module = g_device.CreateShaderModule(&moduleDescriptor);
|
|
const std::array colorTargets{wgpu::ColorTargetState{
|
|
.format = g_graphicsConfig.surfaceConfiguration.format,
|
|
.writeMask = wgpu::ColorWriteMask::All,
|
|
}};
|
|
const wgpu::FragmentState fragmentState{
|
|
.module = module,
|
|
.entryPoint = "fs_main",
|
|
.targetCount = colorTargets.size(),
|
|
.targets = colorTargets.data(),
|
|
};
|
|
const std::array bindGroupLayoutEntries{
|
|
wgpu::BindGroupLayoutEntry{
|
|
.binding = 0,
|
|
.visibility = wgpu::ShaderStage::Fragment,
|
|
.sampler =
|
|
wgpu::SamplerBindingLayout{
|
|
.type = wgpu::SamplerBindingType::Filtering,
|
|
},
|
|
},
|
|
wgpu::BindGroupLayoutEntry{
|
|
.binding = 1,
|
|
.visibility = wgpu::ShaderStage::Fragment,
|
|
.texture =
|
|
wgpu::TextureBindingLayout{
|
|
.sampleType = wgpu::TextureSampleType::Float,
|
|
.viewDimension = wgpu::TextureViewDimension::e2D,
|
|
},
|
|
},
|
|
};
|
|
const wgpu::BindGroupLayoutDescriptor bindGroupLayoutDescriptor{
|
|
.entryCount = bindGroupLayoutEntries.size(),
|
|
.entries = bindGroupLayoutEntries.data(),
|
|
};
|
|
g_CopyBindGroupLayout = g_device.CreateBindGroupLayout(&bindGroupLayoutDescriptor);
|
|
const wgpu::PipelineLayoutDescriptor layoutDescriptor{
|
|
.bindGroupLayoutCount = 1,
|
|
.bindGroupLayouts = &g_CopyBindGroupLayout,
|
|
};
|
|
auto pipelineLayout = g_device.CreatePipelineLayout(&layoutDescriptor);
|
|
const wgpu::RenderPipelineDescriptor pipelineDescriptor{
|
|
.layout = pipelineLayout,
|
|
.vertex =
|
|
wgpu::VertexState{
|
|
.module = module,
|
|
.entryPoint = "vs_main",
|
|
},
|
|
.primitive =
|
|
wgpu::PrimitiveState{
|
|
.topology = wgpu::PrimitiveTopology::TriangleList,
|
|
},
|
|
.multisample =
|
|
wgpu::MultisampleState{
|
|
.count = 1,
|
|
.mask = UINT32_MAX,
|
|
},
|
|
.fragment = &fragmentState,
|
|
};
|
|
g_CopyPipeline = g_device.CreateRenderPipeline(&pipelineDescriptor);
|
|
}
|
|
|
|
wgpu::BindGroup create_copy_bind_group(wgpu::TextureView sourceView, wgpu::Sampler sampler) {
|
|
const std::array bindGroupEntries{
|
|
wgpu::BindGroupEntry{
|
|
.binding = 0,
|
|
.sampler = sampler,
|
|
},
|
|
wgpu::BindGroupEntry{
|
|
.binding = 1,
|
|
.textureView = sourceView,
|
|
},
|
|
};
|
|
const wgpu::BindGroupDescriptor bindGroupDescriptor{
|
|
.layout = g_CopyBindGroupLayout,
|
|
.entryCount = bindGroupEntries.size(),
|
|
.entries = bindGroupEntries.data(),
|
|
};
|
|
return g_device.CreateBindGroup(&bindGroupDescriptor);
|
|
}
|
|
|
|
wgpu::BindGroup create_copy_bind_group(const TextureWithSampler& source) {
|
|
return create_copy_bind_group(source.view, source.sampler);
|
|
}
|
|
|
|
static wgpu::BackendType to_wgpu_backend(AuroraBackend backend) {
|
|
switch (backend) {
|
|
case BACKEND_WEBGPU:
|
|
return wgpu::BackendType::WebGPU;
|
|
case BACKEND_D3D11:
|
|
return wgpu::BackendType::D3D11;
|
|
case BACKEND_D3D12:
|
|
return wgpu::BackendType::D3D12;
|
|
case BACKEND_METAL:
|
|
return wgpu::BackendType::Metal;
|
|
case BACKEND_VULKAN:
|
|
return wgpu::BackendType::Vulkan;
|
|
case BACKEND_OPENGL:
|
|
return wgpu::BackendType::OpenGL;
|
|
case BACKEND_OPENGLES:
|
|
return wgpu::BackendType::OpenGLES;
|
|
default:
|
|
return wgpu::BackendType::Null;
|
|
}
|
|
}
|
|
|
|
static bool create_surface() {
|
|
SDL_Window* window = window::get_sdl_window();
|
|
if (window == nullptr) {
|
|
Log.error("Failed to create surface: no window");
|
|
return false;
|
|
}
|
|
const auto chainedDescriptor = utils::SetupWindowAndGetSurfaceDescriptor(window);
|
|
if (!chainedDescriptor) {
|
|
Log.error("Failed to create surface descriptor for current window");
|
|
return false;
|
|
}
|
|
const wgpu::SurfaceDescriptor surfaceDescriptor{
|
|
.nextInChain = chainedDescriptor.get(),
|
|
.label = "Surface",
|
|
};
|
|
release_surface();
|
|
g_surface = g_instance.CreateSurface(&surfaceDescriptor);
|
|
if (!g_surface) {
|
|
Log.error("Failed to create surface");
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool initialize(AuroraBackend auroraBackend) {
|
|
if (!g_instance) {
|
|
Log.info("Creating WebGPU instance");
|
|
const std::array requiredInstanceFeatures{
|
|
wgpu::InstanceFeatureName::TimedWaitAny,
|
|
};
|
|
wgpu::InstanceDescriptor instanceDescriptor{
|
|
.requiredFeatureCount = requiredInstanceFeatures.size(),
|
|
.requiredFeatures = requiredInstanceFeatures.data(),
|
|
};
|
|
#ifdef WEBGPU_DAWN
|
|
// Dawn hides its D3D12 shared-resource feature from adapter enumeration
|
|
// unless unsafe APIs are exposed by the instance. OpenXR's same-device
|
|
// bridge is the only Aurora path that needs it, so keep the wider Dawn API
|
|
// surface scoped to an explicitly requested XR interop instance.
|
|
const std::array xrInteropInstanceToggles{"allow_unsafe_apis"};
|
|
wgpu::DawnTogglesDescriptor instanceToggles({
|
|
.enabledToggleCount = xrInteropInstanceToggles.size(),
|
|
.enabledToggles = xrInteropInstanceToggles.data(),
|
|
});
|
|
if (g_config.xrInterop) {
|
|
instanceDescriptor.nextInChain = &instanceToggles;
|
|
Log.info("Enabling Dawn unsafe APIs for OpenXR resource interop");
|
|
}
|
|
#endif
|
|
#if defined(WEBGPU_DAWN) && !defined(__MINGW32__)
|
|
// DawnNative.h's C++ constructor has an MSVC ABI that cannot cross into llvm-mingw, and the
|
|
// descriptor only restates Dawn's defaults, so use the public WebGPU descriptor here.
|
|
dawn::native::DawnInstanceDescriptor dawnInstanceDescriptor;
|
|
dawnInstanceDescriptor.backendValidationLevel = dawn::native::BackendValidationLevel::Disabled;
|
|
dawnInstanceDescriptor.nextInChain = instanceDescriptor.nextInChain;
|
|
instanceDescriptor.nextInChain = &dawnInstanceDescriptor;
|
|
#endif
|
|
g_instance = wgpu::CreateInstance(&instanceDescriptor);
|
|
if (!g_instance) {
|
|
Log.error("Failed to create WebGPU instance");
|
|
return false;
|
|
}
|
|
}
|
|
const wgpu::BackendType backend = to_wgpu_backend(auroraBackend);
|
|
Log.info("Attempting to initialize {}", magic_enum::enum_name(backend));
|
|
// One call is one backend attempt. aurora::initialize() retries without calling shutdown(), so a
|
|
// leftover adapter would pass the `if (!g_adapter)` guard and mismatch adapter with device.
|
|
g_queue = {};
|
|
g_device = {};
|
|
g_deviceLostReason.store(wgpu::DeviceLostReason::Unknown, std::memory_order_relaxed);
|
|
g_deviceLost.store(false, std::memory_order_release);
|
|
g_adapter = {};
|
|
g_backendType = wgpu::BackendType::Undefined;
|
|
{
|
|
window::SurfaceLock surfaceLock;
|
|
if (!create_surface()) {
|
|
return false;
|
|
}
|
|
}
|
|
{
|
|
wgpu::RequestAdapterOptions options{
|
|
.powerPreference = wgpu::PowerPreference::HighPerformance,
|
|
.backendType = backend,
|
|
.compatibleSurface = g_surface,
|
|
};
|
|
#if defined(WEBGPU_DAWN) && defined(_WIN32)
|
|
RequestAdapterOptionsLuidWire luidOptions{};
|
|
if (backend == wgpu::BackendType::D3D12 && g_config.xrInterop &&
|
|
g_config.hasD3D12AdapterLuid) {
|
|
luidOptions.chain.sType = wgpu::SType::RequestAdapterOptionsLUID;
|
|
luidOptions.adapterLuid.LowPart = g_config.d3d12AdapterLuidLow;
|
|
luidOptions.adapterLuid.HighPart = g_config.d3d12AdapterLuidHigh;
|
|
options.nextInChain = &luidOptions.chain;
|
|
Log.info("Pinning D3D12 adapter to OpenXR LUID {:08x}:{:08x}",
|
|
static_cast<uint32_t>(luidOptions.adapterLuid.HighPart),
|
|
luidOptions.adapterLuid.LowPart);
|
|
}
|
|
#endif
|
|
const auto future = g_instance.RequestAdapter(
|
|
&options, wgpu::CallbackMode::WaitAnyOnly,
|
|
[](wgpu::RequestAdapterStatus status, wgpu::Adapter adapter, wgpu::StringView message) {
|
|
if (status == wgpu::RequestAdapterStatus::Success) {
|
|
g_adapter = std::move(adapter);
|
|
} else {
|
|
Log.warn("Adapter request failed: {}", message);
|
|
const std::string_view reason{message};
|
|
SDL_SetError("Graphics adapter unavailable: %.*s",
|
|
static_cast<int>(std::min<size_t>(reason.size(), 512)), reason.data());
|
|
}
|
|
});
|
|
const auto status = g_instance.WaitAny(future, 5000000000);
|
|
if (status != wgpu::WaitStatus::Success) {
|
|
Log.error("Failed to create {} adapter: {}", magic_enum::enum_name(backend),
|
|
magic_enum::enum_name(status));
|
|
SDL_SetError("Graphics adapter request did not complete within its startup deadline");
|
|
return false;
|
|
}
|
|
if (!g_adapter) {
|
|
Log.error("No {} adapter is available on this system", magic_enum::enum_name(backend));
|
|
return false;
|
|
}
|
|
}
|
|
g_adapter.GetInfo(&g_adapterInfo);
|
|
g_backendType = g_adapterInfo.backendType;
|
|
const auto backendName = magic_enum::enum_name(g_backendType);
|
|
auto adapterName = g_adapterInfo.device;
|
|
if (adapterName.IsUndefined()) {
|
|
adapterName = wgpu::StringView("Unknown");
|
|
}
|
|
auto description = g_adapterInfo.description;
|
|
if (description.IsUndefined()) {
|
|
description = wgpu::StringView("Unknown");
|
|
}
|
|
Log.info("Graphics adapter information\n API: {}\n Device: {} ({})\n Driver: {}", backendName, adapterName,
|
|
magic_enum::enum_name(g_adapterInfo.adapterType), description);
|
|
|
|
uint32_t maxTextureDimension2D = 0;
|
|
{
|
|
wgpu::Limits supportedLimits{};
|
|
g_adapter.GetLimits(&supportedLimits);
|
|
maxTextureDimension2D = supportedLimits.maxTextureDimension2D;
|
|
const wgpu::Limits requiredLimits{
|
|
// Use "best" supported limits
|
|
.maxTextureDimension1D = supportedLimits.maxTextureDimension1D == 0 ? WGPU_LIMIT_U32_UNDEFINED
|
|
: supportedLimits.maxTextureDimension1D,
|
|
.maxTextureDimension2D = supportedLimits.maxTextureDimension2D == 0 ? WGPU_LIMIT_U32_UNDEFINED
|
|
: supportedLimits.maxTextureDimension2D,
|
|
.maxTextureDimension3D = supportedLimits.maxTextureDimension3D == 0 ? WGPU_LIMIT_U32_UNDEFINED
|
|
: supportedLimits.maxTextureDimension3D,
|
|
.maxTextureArrayLayers = supportedLimits.maxTextureArrayLayers == 0 ? WGPU_LIMIT_U32_UNDEFINED
|
|
: supportedLimits.maxTextureArrayLayers,
|
|
.maxDynamicStorageBuffersPerPipelineLayout = supportedLimits.maxDynamicStorageBuffersPerPipelineLayout == 0
|
|
? WGPU_LIMIT_U32_UNDEFINED
|
|
: supportedLimits.maxDynamicStorageBuffersPerPipelineLayout,
|
|
.maxStorageBuffersPerShaderStage = supportedLimits.maxStorageBuffersPerShaderStage == 0
|
|
? WGPU_LIMIT_U32_UNDEFINED
|
|
: supportedLimits.maxStorageBuffersPerShaderStage,
|
|
.minUniformBufferOffsetAlignment =
|
|
supportedLimits.minUniformBufferOffsetAlignment < 64 ? 64 : supportedLimits.minUniformBufferOffsetAlignment,
|
|
.minStorageBufferOffsetAlignment =
|
|
supportedLimits.minStorageBufferOffsetAlignment < 16 ? 16 : supportedLimits.minStorageBufferOffsetAlignment,
|
|
};
|
|
Log.info(
|
|
"Using limits:"
|
|
"\n maxTextureDimension1D: {}"
|
|
"\n maxTextureDimension2D: {}"
|
|
"\n maxTextureDimension3D: {}"
|
|
"\n maxTextureArrayLayers: {}"
|
|
"\n maxDynamicStorageBuffersPerPipelineLayout: {}"
|
|
"\n maxStorageBuffersPerShaderStage: {}"
|
|
"\n minUniformBufferOffsetAlignment: {}"
|
|
"\n minStorageBufferOffsetAlignment: {}",
|
|
requiredLimits.maxTextureDimension1D, requiredLimits.maxTextureDimension2D,
|
|
requiredLimits.maxTextureDimension3D, requiredLimits.maxTextureArrayLayers,
|
|
requiredLimits.maxDynamicStorageBuffersPerPipelineLayout, requiredLimits.maxStorageBuffersPerShaderStage,
|
|
requiredLimits.minUniformBufferOffsetAlignment, requiredLimits.minStorageBufferOffsetAlignment);
|
|
std::vector<wgpu::FeatureName> requiredFeatures;
|
|
bool implicitDeviceSynchronizationSupported = false;
|
|
wgpu::SupportedFeatures supportedFeatures;
|
|
g_adapter.GetFeatures(&supportedFeatures);
|
|
for (size_t i = 0; i < supportedFeatures.featureCount; ++i) {
|
|
const auto feature = supportedFeatures.features[i];
|
|
if (feature == wgpu::FeatureName::TextureCompressionBC) {
|
|
g_bcTexturesSupported = true;
|
|
requiredFeatures.push_back(feature);
|
|
}
|
|
// Per-pass GPU timing for the frame-rate log (gfx::gpu_timing_*). Requesting the feature
|
|
// costs nothing until a pass carries timestamp writes.
|
|
if (feature == wgpu::FeatureName::TimestampQuery) {
|
|
g_timestampQueriesSupported = true;
|
|
requiredFeatures.push_back(feature);
|
|
}
|
|
// The presenter calls device and queue methods while the frame worker encodes, which Dawn only
|
|
// supports with this feature; without it the two race inside the device's dynamic uploader.
|
|
if (feature == wgpu::FeatureName::ImplicitDeviceSynchronization) {
|
|
implicitDeviceSynchronizationSupported = true;
|
|
requiredFeatures.push_back(feature);
|
|
}
|
|
#if defined(WEBGPU_DAWN) && defined(_WIN32)
|
|
if (g_config.xrInterop && g_backendType == wgpu::BackendType::D3D12 &&
|
|
(feature == wgpu::FeatureName::SharedTextureMemoryD3D12Resource ||
|
|
feature == wgpu::FeatureName::SharedFenceDXGISharedHandle)) {
|
|
requiredFeatures.push_back(feature);
|
|
}
|
|
#endif
|
|
#if defined(WEBGPU_DAWN) && defined(__ANDROID__)
|
|
// The OpenXR side shares eyes through AHardwareBuffers ordered by sync
|
|
// file descriptors (lib/webgpu/vulkan_interop.cpp).
|
|
if (g_config.xrInterop && g_backendType == wgpu::BackendType::Vulkan &&
|
|
(feature == wgpu::FeatureName::SharedTextureMemoryAHardwareBuffer ||
|
|
feature == wgpu::FeatureName::SharedFenceSyncFD)) {
|
|
requiredFeatures.push_back(feature);
|
|
}
|
|
#endif
|
|
}
|
|
if (!implicitDeviceSynchronizationSupported) {
|
|
Log.warn(
|
|
"Adapter does not support ImplicitDeviceSynchronization; multi-threaded presentation is "
|
|
"not safe on this device.");
|
|
}
|
|
#ifdef WEBGPU_DAWN
|
|
wgpu::DawnCacheDeviceDescriptor cacheDescriptor({
|
|
.isolationKey = nullptr,
|
|
.loadDataFunction = load_from_cache,
|
|
.storeDataFunction = store_to_cache,
|
|
.functionUserdata = nullptr,
|
|
});
|
|
|
|
std::vector<const char*> enableToggles{
|
|
/* clang-format off */
|
|
#if _WIN32
|
|
"use_dxc",
|
|
#ifndef NDEBUG
|
|
"emit_hlsl_debug_symbols",
|
|
#endif
|
|
#endif
|
|
#ifndef ANDROID
|
|
"use_user_defined_labels_in_backend",
|
|
#endif
|
|
"disable_symbol_renaming",
|
|
"enable_immediate_error_handling",
|
|
/* clang-format on */
|
|
};
|
|
#ifdef NDEBUG
|
|
#if defined(__ANDROID__)
|
|
// `adb shell setprop debug.wiicompiled.validation 1` keeps WebGPU validation
|
|
// and robustness on for a diagnostic run.
|
|
const bool keepValidation = android_debug::property_int("debug.wiicompiled.validation", 0) == 1;
|
|
Log.info("Android WebGPU validation: {}", keepValidation ? "on" : "off");
|
|
if (!keepValidation)
|
|
#endif
|
|
{
|
|
enableToggles.push_back("skip_validation");
|
|
enableToggles.push_back("disable_robustness");
|
|
}
|
|
#endif
|
|
if (g_backendType == wgpu::BackendType::Vulkan) {
|
|
enableToggles.push_back("vulkan_monolithic_pipeline_cache");
|
|
}
|
|
// Dawn quantizes timestamp queries to 100 us for web privacy; the per-pass GPU timing wants
|
|
// the raw values.
|
|
const std::array<const char*, 1> disableToggles{"timestamp_quantization"};
|
|
const wgpu::DawnTogglesDescriptor togglesDescriptor({
|
|
.nextInChain = &cacheDescriptor,
|
|
.enabledToggleCount = enableToggles.size(),
|
|
.enabledToggles = enableToggles.data(),
|
|
.disabledToggleCount = g_timestampQueriesSupported ? disableToggles.size() : 0,
|
|
.disabledToggles = disableToggles.data(),
|
|
});
|
|
#endif
|
|
wgpu::DeviceDescriptor deviceDescriptor;
|
|
#ifdef WEBGPU_DAWN
|
|
deviceDescriptor.nextInChain = &togglesDescriptor;
|
|
#endif
|
|
deviceDescriptor.requiredFeatureCount = requiredFeatures.size();
|
|
deviceDescriptor.requiredFeatures = requiredFeatures.data();
|
|
deviceDescriptor.requiredLimits = &requiredLimits;
|
|
deviceDescriptor.SetUncapturedErrorCallback(
|
|
[](const wgpu::Device& device, wgpu::ErrorType type, wgpu::StringView message) {
|
|
if (g_initialized.load(std::memory_order_acquire)) {
|
|
FATAL("WebGPU error {}: {}", underlying(type), message);
|
|
} else {
|
|
Log.warn("WebGPU error {}: {}", underlying(type), message);
|
|
}
|
|
});
|
|
deviceDescriptor.SetDeviceLostCallback(wgpu::CallbackMode::AllowSpontaneous,
|
|
[](const wgpu::Device& device, wgpu::DeviceLostReason reason,
|
|
wgpu::StringView message) {
|
|
(void)device;
|
|
// Shutdown and backend retry release the final
|
|
// device reference here too, not a real failure.
|
|
if (reason == wgpu::DeviceLostReason::Destroyed) {
|
|
return;
|
|
}
|
|
// Via string_view, so Dawn resolves a
|
|
// WGPU_STRLEN length instead of SIZE_MAX.
|
|
const std::string_view text{message};
|
|
const size_t copied =
|
|
std::min(text.size(), g_deviceLostMessage.size() - 1);
|
|
if (copied > 0) {
|
|
std::memcpy(g_deviceLostMessage.data(), text.data(), copied);
|
|
}
|
|
g_deviceLostMessage[copied] = '\0';
|
|
g_deviceLostReason.store(reason, std::memory_order_relaxed);
|
|
g_deviceLost.store(true, std::memory_order_release);
|
|
});
|
|
#if defined(__ANDROID__)
|
|
// Foveated eye rendering (fdm.hpp). `adb shell setprop debug.wiicompiled.fdm 0` keeps the density
|
|
// maps off, and their pipeline flag with them, whatever the settings say; 1 asks for them anyway.
|
|
const int fdmOverride = android_debug::property_int("debug.wiicompiled.fdm", -1);
|
|
fdm::request(g_backendType == wgpu::BackendType::Vulkan &&
|
|
(fdmOverride >= 0 ? fdmOverride == 1 : g_config.xrFragmentDensityMap));
|
|
#elif defined(__linux__)
|
|
// Desktop Linux (SteamOS on the Steam Frame), with a Dawn built by Launcher/build-dawn-linux.sh.
|
|
// AURORA_FDM=0 or 1 overrides the settings, as debug.wiicompiled.fdm does on the Quest.
|
|
const char* fdmOverride = std::getenv("AURORA_FDM");
|
|
fdm::request(g_backendType == wgpu::BackendType::Vulkan &&
|
|
(fdmOverride != nullptr && *fdmOverride != '\0' ? std::strcmp(fdmOverride, "1") == 0
|
|
: g_config.xrFragmentDensityMap));
|
|
#endif
|
|
const auto future =
|
|
g_adapter.RequestDevice(&deviceDescriptor, wgpu::CallbackMode::WaitAnyOnly,
|
|
[](wgpu::RequestDeviceStatus status, wgpu::Device device, wgpu::StringView message) {
|
|
if (status == wgpu::RequestDeviceStatus::Success) {
|
|
g_device = std::move(device);
|
|
} else {
|
|
Log.warn("Device request failed: {}", message);
|
|
const std::string_view reason{message};
|
|
SDL_SetError("Graphics device unavailable: %.*s",
|
|
static_cast<int>(std::min<size_t>(reason.size(), 512)),
|
|
reason.data());
|
|
}
|
|
});
|
|
const auto status = g_instance.WaitAny(future, 5000000000);
|
|
if (status != wgpu::WaitStatus::Success) {
|
|
Log.error("Failed to create device: {}", magic_enum::enum_name(status));
|
|
SDL_SetError("Graphics device request did not complete within its startup deadline");
|
|
return false;
|
|
}
|
|
if (!g_device) {
|
|
return false;
|
|
}
|
|
g_device.SetLoggingCallback([](wgpu::LoggingType type, wgpu::StringView message) {
|
|
AuroraLogLevel level = LOG_FATAL;
|
|
switch (type) {
|
|
case wgpu::LoggingType::Verbose:
|
|
level = LOG_DEBUG;
|
|
break;
|
|
case wgpu::LoggingType::Info:
|
|
level = LOG_INFO;
|
|
break;
|
|
case wgpu::LoggingType::Warning:
|
|
level = LOG_WARNING;
|
|
break;
|
|
case wgpu::LoggingType::Error:
|
|
level = LOG_ERROR;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
Log.report(level, "WebGPU message: {}", message);
|
|
});
|
|
fdm::device_created();
|
|
}
|
|
g_queue = g_device.GetQueue();
|
|
|
|
const wgpu::Status status = g_surface.GetCapabilities(g_adapter, &g_surfaceCapabilities);
|
|
if (status != wgpu::Status::Success) {
|
|
Log.error("Failed to get surface capabilities: {}", magic_enum::enum_name(status));
|
|
return false;
|
|
}
|
|
if (g_surfaceCapabilities.formatCount == 0) {
|
|
Log.error("Surface has no formats");
|
|
return false;
|
|
}
|
|
if (g_surfaceCapabilities.presentModeCount == 0) {
|
|
Log.error("Surface has no present modes");
|
|
return false;
|
|
}
|
|
auto surfaceFormat = best_surface_format();
|
|
auto presentMode = best_present_mode();
|
|
Log.info("Using surface format {}, present mode {}", magic_enum::enum_name(surfaceFormat),
|
|
magic_enum::enum_name(presentMode));
|
|
const auto size = window::get_window_size();
|
|
g_graphicsConfig = GraphicsConfig{
|
|
.surfaceConfiguration =
|
|
wgpu::SurfaceConfiguration{
|
|
.format = surfaceFormat,
|
|
.usage = wgpu::TextureUsage::RenderAttachment | wgpu::TextureUsage::CopySrc,
|
|
.width = size.native_fb_width,
|
|
.height = size.native_fb_height,
|
|
.presentMode = presentMode,
|
|
},
|
|
.depthFormat = wgpu::TextureFormat::Depth32Float,
|
|
.msaaSamples = g_config.msaa,
|
|
.textureAnisotropy = g_config.maxTextureAnisotropy,
|
|
.maxTextureDimension2D = maxTextureDimension2D,
|
|
};
|
|
create_copy_pipeline();
|
|
{
|
|
window::SurfaceLock surfaceLock;
|
|
resize_swapchain(size.fb_width, size.fb_height, size.native_fb_width, size.native_fb_height, true);
|
|
}
|
|
g_initialized.store(true, std::memory_order_release);
|
|
return true;
|
|
}
|
|
|
|
void fail_if_device_lost() noexcept {
|
|
if (!g_deviceLost.load(std::memory_order_acquire)) {
|
|
return;
|
|
}
|
|
|
|
// Several frame-owning threads can observe loss, so serialize escalation: one thread logs and the
|
|
// rest wait for termination instead of submitting more work to a lost device.
|
|
static std::mutex fatalMutex;
|
|
const std::lock_guard lock(fatalMutex);
|
|
const auto reason = g_deviceLostReason.load(std::memory_order_relaxed);
|
|
const char* const detail = g_deviceLostMessage.data();
|
|
if (detail[0] != '\0') {
|
|
Log.fatal("WebGPU device was lost ({}: {}). Rendering cannot continue safely; restart the application.",
|
|
magic_enum::enum_name(reason), detail);
|
|
} else {
|
|
Log.fatal("WebGPU device was lost ({}). Rendering cannot continue safely; restart the application.",
|
|
magic_enum::enum_name(reason));
|
|
}
|
|
}
|
|
|
|
void serialize_pipeline_caches() noexcept {
|
|
#if defined(WEBGPU_DAWN)
|
|
// Only the Vulkan backend keeps a monolithic VkPipelineCache (toggle above). Dawn writes it to
|
|
// the blob cache from PerformIdleTasks, and only when a pipeline was compiled since the last
|
|
// store, so calling this when nothing changed is cheap.
|
|
if (!g_device || g_backendType != wgpu::BackendType::Vulkan) {
|
|
return;
|
|
}
|
|
#if defined(_WIN32)
|
|
// The Windows product links the Dawn DLL from llvm-mingw, which cannot call the exported C++
|
|
// symbol directly; resolve its MSVC-mangled name instead.
|
|
using PerformIdleTasksFn = void(*)(const wgpu::Device*);
|
|
static const auto performIdleTasks = []() -> PerformIdleTasksFn {
|
|
const HMODULE dawnModule = GetModuleHandleW(L"webgpu_dawn.dll");
|
|
if (dawnModule == nullptr) {
|
|
return nullptr;
|
|
}
|
|
return reinterpret_cast<PerformIdleTasksFn>(reinterpret_cast<void*>(
|
|
GetProcAddress(dawnModule, "?PerformIdleTasks@native@dawn@@YAXAEBVDevice@wgpu@@@Z")));
|
|
}();
|
|
if (performIdleTasks != nullptr) {
|
|
performIdleTasks(&g_device);
|
|
}
|
|
#elif !defined(__MINGW32__)
|
|
// Static Dawn (Android, Linux): DawnNative.h is included above.
|
|
dawn::native::PerformIdleTasks(g_device);
|
|
#endif
|
|
#endif
|
|
}
|
|
|
|
void shutdown() {
|
|
serialize_pipeline_caches();
|
|
g_initialized.store(false, std::memory_order_release);
|
|
g_CopyBindGroupLayout = {};
|
|
g_CopyPipeline = {};
|
|
g_CopyBindGroup = {};
|
|
g_frameBuffer = {};
|
|
g_frameBufferResolved = {};
|
|
g_depthBuffer = {};
|
|
g_queue = {};
|
|
g_surface = {};
|
|
g_device = {};
|
|
g_adapter = {};
|
|
g_instance = {};
|
|
|
|
cache_shutdown();
|
|
}
|
|
|
|
void release_surface() noexcept {
|
|
const bool hadSurface = static_cast<bool>(g_surface);
|
|
if (g_surface) {
|
|
g_surface.Unconfigure();
|
|
}
|
|
g_surface = {};
|
|
if (hadSurface && g_instance && g_device && g_queue) {
|
|
const auto future = g_queue.OnSubmittedWorkDone(
|
|
wgpu::CallbackMode::WaitAnyOnly, [](wgpu::QueueWorkDoneStatus, wgpu::StringView) {});
|
|
g_instance.WaitAny(future, 1000000000);
|
|
}
|
|
}
|
|
|
|
bool refresh_surface(bool recreate) {
|
|
if (!g_instance || !g_device) {
|
|
return false;
|
|
}
|
|
if (!window::is_presentable()) {
|
|
release_surface();
|
|
return false;
|
|
}
|
|
if ((!g_surface || recreate) && !create_surface()) {
|
|
return false;
|
|
}
|
|
uint32_t width = g_graphicsConfig.surfaceConfiguration.width;
|
|
uint32_t height = g_graphicsConfig.surfaceConfiguration.height;
|
|
uint32_t native_width = width;
|
|
uint32_t native_height = height;
|
|
if (window::get_sdl_window() != nullptr) {
|
|
const auto size = window::get_window_size();
|
|
width = size.fb_width;
|
|
height = size.fb_height;
|
|
native_width = size.native_fb_width;
|
|
native_height = size.native_fb_height;
|
|
}
|
|
if (width != 0 && height != 0) {
|
|
resize_swapchain(width, height, native_width, native_height, true);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
void resize_swapchain(uint32_t width, uint32_t height, uint32_t native_width, uint32_t native_height, bool force) {
|
|
if (!g_surface || !g_device || width == 0 || height == 0 || native_height == 0 || native_width == 0) {
|
|
return;
|
|
}
|
|
|
|
uint32_t render_width = width;
|
|
uint32_t render_height = height;
|
|
const auto [efbWidth, efbHeight] = vi::configured_fb_size();
|
|
if (efbWidth != 0 && efbHeight != 0) {
|
|
render_width = std::max(render_width, efbWidth);
|
|
render_height = std::max(render_height, efbHeight);
|
|
}
|
|
|
|
const auto requestedRenderSize = RenderTargetSize{render_width, render_height};
|
|
const auto clampedRenderSize = clamp_frame_buffer_size(render_width, render_height);
|
|
render_width = clampedRenderSize.width;
|
|
render_height = clampedRenderSize.height;
|
|
if (requestedRenderSize.width != render_width || requestedRenderSize.height != render_height) {
|
|
Log.warn(
|
|
"Render target {}x{} exceeds the safe framebuffer budget (adapter max {}, practical max {} / {} "
|
|
"pixels); clamping to {}x{}",
|
|
requestedRenderSize.width, requestedRenderSize.height, g_graphicsConfig.maxTextureDimension2D,
|
|
render_size_limits::kMaxFramebufferDimension, render_size_limits::kMaxFramebufferPixels, render_width,
|
|
render_height);
|
|
}
|
|
|
|
const bool sizeChanged = g_graphicsConfig.surfaceConfiguration.width != native_width ||
|
|
g_graphicsConfig.surfaceConfiguration.height != native_height ||
|
|
g_frameBuffer.size.width != render_width || g_frameBuffer.size.height != render_height;
|
|
if (!force && !sizeChanged) {
|
|
return;
|
|
}
|
|
if (sizeChanged) {
|
|
gx::clear_display_copy_cache();
|
|
gfx::clear_caches();
|
|
clear_present_source_override();
|
|
}
|
|
g_graphicsConfig.surfaceConfiguration.width = native_width;
|
|
g_graphicsConfig.surfaceConfiguration.height = native_height;
|
|
auto surfaceConfiguration = g_graphicsConfig.surfaceConfiguration;
|
|
surfaceConfiguration.device = g_device;
|
|
g_surface.Configure(&surfaceConfiguration);
|
|
if (!sizeChanged) {
|
|
// Forced reconfigure at an unchanged size (present-mode change or recreated surface). The
|
|
// offscreen targets are not swapchain images, so reallocating them would only stall the frame.
|
|
return;
|
|
}
|
|
g_frameBuffer = create_render_texture(render_width, render_height, true);
|
|
g_frameBufferResolved = create_render_texture(render_width, render_height, false);
|
|
g_depthBuffer = create_depth_texture(render_width, render_height);
|
|
g_CopyBindGroup = create_copy_bind_group(present_source());
|
|
}
|
|
} // namespace aurora::webgpu
|