26#include "common/config.h"
34#include <assimp/matrix3x3.h>
35#include <assimp/matrix4x4.h>
36#include <assimp/mesh.h>
37#include <assimp/vector3.h>
49#include <glm/gtc/constants.hpp>
52#include <SDL2/SDL_syswm.h>
53#if defined(__APPLE__) && !defined(__EMSCRIPTEN__)
54#include <objc/message.h>
55#include <objc/runtime.h>
62#if defined(__EMSCRIPTEN__)
63#include <emscripten/emscripten.h>
71wgpu::Sampler createLinearSampler(wgpu::Device& dev) {
72 WGPUSamplerDescriptor
d{};
73 d.label =
sv(
"eve_linear");
74 d.addressModeU = WGPUAddressMode_ClampToEdge;
75 d.addressModeV = WGPUAddressMode_ClampToEdge;
76 d.addressModeW = WGPUAddressMode_ClampToEdge;
77 d.magFilter = WGPUFilterMode_Linear;
78 d.minFilter = WGPUFilterMode_Linear;
79 d.mipmapFilter = WGPUMipmapFilterMode_Linear;
81 d.lodMaxClamp = 1000.f;
83 return dev.CreateSampler(
reinterpret_cast<const wgpu::SamplerDescriptor*
>(&
d));
88 while (
p <
v)
p <<= 1;
97 for (uint32_t i = 0; i < kFramesInFlight; ++i) {
98 uboArenas.emplace_back();
99 vertexArenas.emplace_back();
104 destroyDeferredLightingResources();
114 if (deviceInitDone) {
119 throw Exception(
"Graphics::initHeadless: already initialized with a window");
127 instance = std::move(dev.instance);
128 adapter = std::move(dev.adapter);
129 device = std::move(dev.device);
130 queue = std::move(dev.queue);
131 caps = std::move(dev.caps);
132 surfaceFormat = WGPUTextureFormat_BGRA8Unorm;
134 createPipelineResources();
135 createShadowResources();
136 createDefaultTextures();
141 deviceInitDone =
true;
145 sdlWindow = nativeWindow;
146 if (deviceInitDone)
return;
152 instance = std::move(dev.instance);
153 adapter = std::move(dev.adapter);
154 device = std::move(dev.device);
155 queue = std::move(dev.queue);
156 caps = std::move(dev.caps);
159 WGPUSurfaceDescriptor surfDesc{};
160 surfDesc.label =
sv(
"eve_surface");
161#if defined(__EMSCRIPTEN__)
162 WGPUEmscriptenSurfaceSourceCanvasHTMLSelector canvasSel{};
163 canvasSel.chain.sType = WGPUSType_EmscriptenSurfaceSourceCanvasHTMLSelector;
164 canvasSel.selector =
sv(
"#canvas");
165 surfDesc.nextInChain = &canvasSel.chain;
166 surface = instance.CreateSurface(
reinterpret_cast<const wgpu::SurfaceDescriptor*
>(&surfDesc));
167 surfaceFormat = WGPUTextureFormat_BGRA8Unorm;
169 wgpu::SurfaceDescriptor nativeSurfDesc{};
170 nativeSurfDesc.label =
"eve_surface";
171 SDL_SysWMinfo wminfo;
172 SDL_VERSION(&wminfo.version);
173 if (!SDL_GetWindowWMInfo(
static_cast<SDL_Window*
>(sdlWindow), &wminfo))
174 throw Exception(
"WebGPU: SDL_GetWindowWMInfo failed: %s", SDL_GetError());
176 wgpu::SurfaceSourceWindowsHWND winChain{};
177 winChain.hwnd = wminfo.info.win.window;
178 winChain.hinstance = GetModuleHandle(
nullptr);
179 nativeSurfDesc.nextInChain = &winChain;
180 surface = instance.CreateSurface(&nativeSurfDesc);
181#elif defined(__linux__)
182 if (wminfo.subsystem == SDL_SYSWM_X11) {
183 wgpu::SurfaceSourceXlibWindow x11Chain{};
184 x11Chain.display = wminfo.info.x11.display;
185 x11Chain.window = wminfo.info.x11.window;
186 nativeSurfDesc.nextInChain = &x11Chain;
187 surface = instance.CreateSurface(&nativeSurfDesc);
188#if defined(SDL_VIDEO_DRIVER_WAYLAND)
189 }
else if (wminfo.subsystem == SDL_SYSWM_WAYLAND) {
190 wgpu::SurfaceSourceWaylandSurface wlChain{};
191 wlChain.display = wminfo.info.wl.display;
192 wlChain.surface = wminfo.info.wl.surface;
193 nativeSurfDesc.nextInChain = &wlChain;
194 surface = instance.CreateSurface(&nativeSurfDesc);
197 throw Exception(
"WebGPU: unsupported SDL window subsystem on Linux");
199#elif defined(__APPLE__)
202 using SendId =
id (*)(
id, SEL);
203 using SendVoidId = void (*)(
id, SEL,
id);
204 using SendVoidBool = void (*)(
id, SEL, BOOL);
205 using SendIsKind = BOOL (*)(
id, SEL, Class);
206 const auto sendId =
reinterpret_cast<SendId
>(objc_msgSend);
207 const auto sendVoidId =
reinterpret_cast<SendVoidId
>(objc_msgSend);
208 const auto sendVoidBool =
reinterpret_cast<SendVoidBool
>(objc_msgSend);
209 const auto sendIsKind =
reinterpret_cast<SendIsKind
>(objc_msgSend);
210 id window =
reinterpret_cast<id>(wminfo.info.cocoa.window);
211 id view = sendId(window, sel_registerName(
"contentView"));
212 if (
view == nil)
throw Exception(
"WebGPU: SDL Cocoa window has no content view");
213 sendVoidBool(
view, sel_registerName(
"setWantsLayer:"), YES);
214 id layer = sendId(
view, sel_registerName(
"layer"));
215 Class metalLayerClass = objc_getClass(
"CAMetalLayer");
216 if (metalLayerClass == Nil)
throw Exception(
"WebGPU: CAMetalLayer class unavailable");
217 if (
layer == nil || !sendIsKind(
layer, sel_registerName(
"isKindOfClass:"), metalLayerClass)) {
218 layer = sendId(
reinterpret_cast<id>(metalLayerClass), sel_registerName(
"layer"));
219 sendVoidId(
view, sel_registerName(
"setLayer:"),
layer);
221 if (
layer == nil)
throw Exception(
"WebGPU: failed to create CAMetalLayer");
222 WGPUSurfaceSourceMetalLayer metalChain{};
223 metalChain.chain.sType = WGPUSType_SurfaceSourceMetalLayer;
224 metalChain.layer =
layer;
225 surfDesc.nextInChain = &metalChain.chain;
226 surface = instance.CreateSurface(
reinterpret_cast<const wgpu::SurfaceDescriptor*
>(&surfDesc));
228 throw Exception(
"WebGPU: unsupported native platform for surface creation");
230 if (!surface)
throw Exception(
"WebGPU: surface creation failed");
233 WGPUSurfaceCapabilities caps{};
234 if (wgpuSurfaceGetCapabilities(surface.Get(), adapter.Get(), &caps) == WGPUStatus_Success && caps.formatCount > 0) {
239 surfaceFormat = caps.formats[0];
240 for (
size_t i = 0; i < caps.formatCount; ++i) {
241 if (caps.formats[i] == WGPUTextureFormat_BGRA8Unorm) {
242 surfaceFormat = caps.formats[i];
245 if (caps.formats[i] == WGPUTextureFormat_RGBA8Unorm) {
246 surfaceFormat = caps.formats[i];
250 wgpuSurfaceCapabilitiesFreeMembers(caps);
253 swapchainConfigured =
false;
257 createPipelineResources();
258 createShadowResources();
259 createDefaultTextures();
261 deviceInitDone =
true;
267 markSwapchainDirty();
281 pixelH = pixelheight;
286void Graphics::configureSurface(
int width,
int height) {
287 if (!surface || !device ||
width <= 0 ||
height <= 0)
return;
288 WGPUSurfaceConfiguration cfg{};
289 cfg.nextInChain =
nullptr;
290 cfg.device = device.Get();
291 cfg.width =
static_cast<uint32_t
>(
width);
292 cfg.height =
static_cast<uint32_t
>(
height);
293 cfg.format = surfaceFormat;
294 WGPUSurfaceCapabilities caps{};
295 surfaceCanCopySrc = wgpuSurfaceGetCapabilities(surface.Get(), adapter.Get(), &caps) == WGPUStatus_Success &&
296 (caps.usages & WGPUTextureUsage_CopySrc) != 0;
297 wgpuSurfaceCapabilitiesFreeMembers(caps);
298 cfg.usage = WGPUTextureUsage_RenderAttachment | (surfaceCanCopySrc ? WGPUTextureUsage_CopySrc : 0);
299 cfg.viewFormatCount = 0;
300 cfg.viewFormats =
nullptr;
302 cfg.presentMode = WGPUPresentMode_Fifo;
306 cfg.alphaMode = WGPUCompositeAlphaMode_Opaque;
307 surface.Configure(
reinterpret_cast<const wgpu::SurfaceConfiguration*
>(&cfg));
308 swapchainConfigured =
true;
310 int w = pixelW > 0 ? pixelW :
width;
311 int h = pixelH > 0 ? pixelH :
height;
312 if (
w > 0 &&
h > 0) {
313 createSceneColorResources(
w,
h);
314 createShadowResources();
318void Graphics::rebuildSwapchainIfNeeded() {
319 if (!swapchainConfigured && surface && logicalW > 0 && logicalH > 0) {
320 configureSurface(logicalW, logicalH);
328void Graphics::createDefaultTextures() {
329 uint8_t white[4] = {255, 255, 255, 255};
333 uint8_t flatNrm[4] = {128, 128, 255, 255};
334 flatNormalTexture =
static_cast<GpuTexture*
>(
newTexture(1, 1, flatNrm,
false,
false)->
gpuHandle);
335 flatNormalTexture3D = flatNormalTexture;
337 uint8_t flatH[4] = {0, 0, 0, 255};
338 flatHeightTexture3D =
static_cast<GpuTexture*
>(
newTexture(1, 1, flatH,
false,
false)->
gpuHandle);
343 auto* gpu =
new GpuTexture();
344 WGPUTextureDescriptor td{};
345 td.label =
sv(
"eve_flat_depth");
346 td.dimension = WGPUTextureDimension_2D;
349 td.format = WGPUTextureFormat_Depth32Float;
350 td.mipLevelCount = 1;
351 td.usage = WGPUTextureUsage_TextureBinding;
352 gpu->texture = device.CreateTexture(
reinterpret_cast<const wgpu::TextureDescriptor*
>(&td));
353 WGPUTextureViewDescriptor vd{};
354 vd.format = WGPUTextureFormat_Depth32Float;
355 vd.dimension = WGPUTextureViewDimension_2D;
357 vd.mipLevelCount = 1;
358 vd.baseArrayLayer = 0;
359 vd.arrayLayerCount = 1;
360 gpu->view = gpu->texture.CreateView(
reinterpret_cast<const wgpu::TextureViewDescriptor*
>(&vd));
363 flatDepthTexture3D = gpu;
370 auto* gpu =
new GpuTexture();
371 WGPUTextureDescriptor td{};
372 td.label =
sv(
"eve_default_shadow_depth");
373 td.dimension = WGPUTextureDimension_2D;
376 td.format = WGPUTextureFormat_Depth32Float;
377 td.mipLevelCount = 1;
378 td.usage = WGPUTextureUsage_TextureBinding;
379 gpu->texture = device.CreateTexture(
reinterpret_cast<const wgpu::TextureDescriptor*
>(&td));
380 WGPUTextureViewDescriptor vd{};
381 vd.format = WGPUTextureFormat_Depth32Float;
382 vd.dimension = WGPUTextureViewDimension_2DArray;
384 vd.mipLevelCount = 1;
385 vd.baseArrayLayer = 0;
387 gpu->view = gpu->texture.CreateView(
reinterpret_cast<const wgpu::TextureViewDescriptor*
>(&vd));
388 WGPUSamplerDescriptor sd{};
389 sd.label =
sv(
"eve_default_shadow_sampler");
390 sd.addressModeU = WGPUAddressMode_ClampToEdge;
391 sd.addressModeV = WGPUAddressMode_ClampToEdge;
392 sd.addressModeW = WGPUAddressMode_ClampToEdge;
393 sd.magFilter = WGPUFilterMode_Linear;
394 sd.minFilter = WGPUFilterMode_Linear;
395 sd.mipmapFilter = WGPUMipmapFilterMode_Nearest;
396 sd.compare = WGPUCompareFunction_LessEqual;
397 sd.maxAnisotropy = 1.f;
398 gpu->sampler = device.CreateSampler(
reinterpret_cast<const wgpu::SamplerDescriptor*
>(&sd));
399 defaultShadowTex = gpu;
405 uint8_t cubeFace[4] = {0, 0, 0, 0};
406 uint8_t cubeData[24];
407 for (
int f = 0;
f < 6; ++
f) std::memcpy(cubeData +
f * 4, cubeFace, 4);
412 mainSampler = makeSampler(def, 1);
414 const uint8_t transparent[4] = {0, 0, 0, 0};
415 const uint8_t decalNormal[4] = {128, 128, 255, 0};
416 const uint8_t decalParams[4] = {0, 0, 0, 0};
417 decalFlatAlbedo =
newTexture(1, 1, transparent);
418 decalFlatNormal =
newTexture(1, 1, decalNormal);
419 decalFlatParams =
newTexture(1, 1, decalParams);
426void Graphics::createPipelineResources() {
432 createMesh3DPipelines();
439wgpu::BindGroupLayout Graphics::make2DBindGroupLayout() {
440 BindGroupLayoutBuilder
b;
442 b.texture(0, wgpu::ShaderStage::Fragment, wgpu::TextureSampleType::Float, wgpu::TextureViewDimension::e2D);
444 b.texture(1, wgpu::ShaderStage::Fragment, wgpu::TextureSampleType::Float, wgpu::TextureViewDimension::e2D);
446 b.sampler(2, wgpu::ShaderStage::Fragment, wgpu::SamplerBindingType::Filtering);
448 b.sampler(3, wgpu::ShaderStage::Fragment, wgpu::SamplerBindingType::Filtering);
451 b.buffer(4, wgpu::ShaderStage::Vertex | wgpu::ShaderStage::Fragment, wgpu::BufferBindingType::Uniform,
true,
453 b.texture(5, wgpu::ShaderStage::Fragment, wgpu::TextureSampleType::Float, wgpu::TextureViewDimension::e2D);
454 b.sampler(6, wgpu::ShaderStage::Fragment, wgpu::SamplerBindingType::Filtering);
455 b.texture(7, wgpu::ShaderStage::Fragment, wgpu::TextureSampleType::Float, wgpu::TextureViewDimension::e2D);
456 b.sampler(8, wgpu::ShaderStage::Fragment, wgpu::SamplerBindingType::Filtering);
457 b.texture(9, wgpu::ShaderStage::Fragment, wgpu::TextureSampleType::Float, wgpu::TextureViewDimension::e2D);
458 b.sampler(10, wgpu::ShaderStage::Fragment, wgpu::SamplerBindingType::Filtering);
459 return b.build(device,
"eve_2d");
462wgpu::BindGroupLayout Graphics::makeMesh3DBindGroupLayout() {
463 BindGroupLayoutBuilder
b;
465 b.buffer(0, wgpu::ShaderStage::Vertex | wgpu::ShaderStage::Fragment, wgpu::BufferBindingType::Uniform,
true,
468 b.texture(1, wgpu::ShaderStage::Fragment, wgpu::TextureSampleType::Float, wgpu::TextureViewDimension::e2D);
470 b.texture(2, wgpu::ShaderStage::Fragment, wgpu::TextureSampleType::Float, wgpu::TextureViewDimension::e2D);
472 b.texture(3, wgpu::ShaderStage::Fragment, wgpu::TextureSampleType::Float, wgpu::TextureViewDimension::Cube);
474 b.buffer(4, wgpu::ShaderStage::Fragment, wgpu::BufferBindingType::Uniform,
true,
sizeof(ShadowUBO));
476 b.texture(5, wgpu::ShaderStage::Fragment, wgpu::TextureSampleType::Depth, wgpu::TextureViewDimension::e2DArray);
478 b.texture(6, wgpu::ShaderStage::Fragment, wgpu::TextureSampleType::Float, wgpu::TextureViewDimension::e2D);
480 b.sampler(7, wgpu::ShaderStage::Fragment, wgpu::SamplerBindingType::Filtering);
482 b.sampler(8, wgpu::ShaderStage::Fragment, wgpu::SamplerBindingType::Comparison);
485 b.texture(9, wgpu::ShaderStage::Fragment, wgpu::TextureSampleType::Depth, wgpu::TextureViewDimension::e2D);
487 b.texture(10, wgpu::ShaderStage::Fragment, wgpu::TextureSampleType::Float, wgpu::TextureViewDimension::e2D);
489 b.sampler(11, wgpu::ShaderStage::Fragment, wgpu::SamplerBindingType::Filtering);
490 for (uint32_t i = 12; i <= 14; ++i) {
491 b.texture(i, wgpu::ShaderStage::Fragment, wgpu::TextureSampleType::Float, wgpu::TextureViewDimension::e2D);
495 b.buffer(15, wgpu::ShaderStage::Vertex | wgpu::ShaderStage::Fragment, wgpu::BufferBindingType::Uniform,
true,
497 b.texture(16, wgpu::ShaderStage::Fragment, wgpu::TextureSampleType::Float, wgpu::TextureViewDimension::Cube);
498 b.texture(17, wgpu::ShaderStage::Fragment, wgpu::TextureSampleType::Float, wgpu::TextureViewDimension::Cube);
499 b.texture(18, wgpu::ShaderStage::Fragment, wgpu::TextureSampleType::Float, wgpu::TextureViewDimension::e2D);
500 b.sampler(19, wgpu::ShaderStage::Fragment, wgpu::SamplerBindingType::Filtering);
501 b.buffer(21, wgpu::ShaderStage::Vertex, wgpu::BufferBindingType::ReadOnlyStorage,
false, 64);
502 return b.build(device,
"eve_mesh3d");
505wgpu::BindGroupLayout Graphics::makeShadowBindGroupLayout() {
506 BindGroupLayoutBuilder
b;
507 b.buffer(0, wgpu::ShaderStage::Vertex | wgpu::ShaderStage::Fragment,
508 wgpu::BufferBindingType::Uniform,
true,
sizeof(SkinPassUBO));
509 b.texture(1, wgpu::ShaderStage::Fragment, wgpu::TextureSampleType::Float, wgpu::TextureViewDimension::e2D);
510 b.sampler(2, wgpu::ShaderStage::Fragment, wgpu::SamplerBindingType::Filtering);
511 b.buffer(3, wgpu::ShaderStage::Vertex, wgpu::BufferBindingType::ReadOnlyStorage,
false, 64);
512 return b.build(device,
"eve_shadow");
515wgpu::BindGroupLayout Graphics::makeGbufferBindGroupLayout() {
516 BindGroupLayoutBuilder
b;
518 b.buffer(0, wgpu::ShaderStage::Vertex | wgpu::ShaderStage::Fragment, wgpu::BufferBindingType::Uniform,
true,
519 sizeof(SkinPassUBO));
521 b.texture(1, wgpu::ShaderStage::Fragment, wgpu::TextureSampleType::Float, wgpu::TextureViewDimension::e2D);
523 b.sampler(2, wgpu::ShaderStage::Fragment, wgpu::SamplerBindingType::Filtering);
524 b.buffer(3, wgpu::ShaderStage::Vertex, wgpu::BufferBindingType::ReadOnlyStorage,
false, 64);
525 return b.build(device,
"eve_gbuffer");
528wgpu::BindGroupLayout Graphics::makeDecalBindGroupLayout() {
529 BindGroupLayoutBuilder
b;
530 b.buffer(0, wgpu::ShaderStage::Fragment, wgpu::BufferBindingType::Uniform,
true, 256);
531 for (uint32_t i = 1; i <= 3; ++i) {
532 b.texture(i, wgpu::ShaderStage::Fragment, wgpu::TextureSampleType::Float, wgpu::TextureViewDimension::e2D);
534 b.texture(4, wgpu::ShaderStage::Fragment, wgpu::TextureSampleType::Depth, wgpu::TextureViewDimension::e2D);
535 b.texture(5, wgpu::ShaderStage::Fragment, wgpu::TextureSampleType::Float, wgpu::TextureViewDimension::e2D);
536 b.sampler(6, wgpu::ShaderStage::Fragment, wgpu::SamplerBindingType::Filtering);
537 return b.build(device,
"eve_decal");
540wgpu::BindGroupLayout Graphics::makeVoxelBindGroupLayout() {
541 BindGroupLayoutBuilder
b;
543 b.buffer(0, wgpu::ShaderStage::Vertex, wgpu::BufferBindingType::Uniform,
true, 112);
545 b.texture(1, wgpu::ShaderStage::Fragment, wgpu::TextureSampleType::Float, wgpu::TextureViewDimension::e2D);
547 b.sampler(2, wgpu::ShaderStage::Fragment, wgpu::SamplerBindingType::Filtering);
548 return b.build(device,
"eve_voxel");
551wgpu::PipelineLayout Graphics::make2DPipelineLayout() {
552 wgpu::PipelineLayoutDescriptor
d{};
553 d.label =
"eve_2d_layout";
554 d.bindGroupLayoutCount = 1;
555 d.bindGroupLayouts = &tex2DSetLayout;
556 return device.CreatePipelineLayout(&
d);
559wgpu::PipelineLayout Graphics::makeMesh3DPipelineLayout() {
560 wgpu::PipelineLayoutDescriptor
d{};
561 d.label =
"eve_mesh3d_layout";
562 d.bindGroupLayoutCount = 1;
563 d.bindGroupLayouts = &mesh3dSetLayout;
564 return device.CreatePipelineLayout(&
d);
567wgpu::BindGroupLayout Graphics::makeMesh3DClusteredBindGroupLayout() {
568 BindGroupLayoutBuilder
b;
570 b.buffer(0, wgpu::ShaderStage::Vertex | wgpu::ShaderStage::Fragment, wgpu::BufferBindingType::Uniform,
true,
571 sizeof(Mesh3DClusteredUBO));
573 b.texture(1, wgpu::ShaderStage::Fragment, wgpu::TextureSampleType::Float, wgpu::TextureViewDimension::e2D);
575 b.texture(2, wgpu::ShaderStage::Fragment, wgpu::TextureSampleType::Float, wgpu::TextureViewDimension::e2D);
577 b.texture(3, wgpu::ShaderStage::Fragment, wgpu::TextureSampleType::Float, wgpu::TextureViewDimension::Cube);
579 b.buffer(4, wgpu::ShaderStage::Fragment, wgpu::BufferBindingType::Uniform,
true,
sizeof(ShadowUBO));
581 b.texture(5, wgpu::ShaderStage::Fragment, wgpu::TextureSampleType::Depth, wgpu::TextureViewDimension::e2DArray);
583 b.texture(6, wgpu::ShaderStage::Fragment, wgpu::TextureSampleType::Float, wgpu::TextureViewDimension::e2D);
585 b.sampler(7, wgpu::ShaderStage::Fragment, wgpu::SamplerBindingType::Filtering);
587 b.sampler(8, wgpu::ShaderStage::Fragment, wgpu::SamplerBindingType::Comparison);
589 b.texture(9, wgpu::ShaderStage::Fragment, wgpu::TextureSampleType::Depth, wgpu::TextureViewDimension::e2D);
591 b.buffer(10, wgpu::ShaderStage::Fragment, wgpu::BufferBindingType::ReadOnlyStorage,
false,
593 b.buffer(11, wgpu::ShaderStage::Fragment, wgpu::BufferBindingType::ReadOnlyStorage,
false,
594 sizeof(ClusterTableEntry));
595 b.buffer(12, wgpu::ShaderStage::Fragment, wgpu::BufferBindingType::ReadOnlyStorage,
false,
sizeof(uint32_t));
597 b.texture(13, wgpu::ShaderStage::Fragment, wgpu::TextureSampleType::Float, wgpu::TextureViewDimension::e2D);
598 b.sampler(14, wgpu::ShaderStage::Fragment, wgpu::SamplerBindingType::Filtering);
599 for (uint32_t i = 15; i <= 17; ++i) {
600 b.texture(i, wgpu::ShaderStage::Fragment, wgpu::TextureSampleType::Float, wgpu::TextureViewDimension::e2D);
603 b.texture(18, wgpu::ShaderStage::Fragment, wgpu::TextureSampleType::Float, wgpu::TextureViewDimension::Cube);
604 b.texture(19, wgpu::ShaderStage::Fragment, wgpu::TextureSampleType::Float, wgpu::TextureViewDimension::Cube);
605 return b.build(device,
"eve_mesh3d_clustered");
608wgpu::PipelineLayout Graphics::makeMesh3DClusteredPipelineLayout() {
609 wgpu::PipelineLayoutDescriptor
d{};
610 d.label =
"eve_mesh3d_clustered_layout";
611 d.bindGroupLayoutCount = 1;
612 d.bindGroupLayouts = &mesh3dClusteredSetLayout;
613 return device.CreatePipelineLayout(&
d);
616wgpu::PipelineLayout Graphics::makeShadowPipelineLayout() {
617 wgpu::PipelineLayoutDescriptor
d{};
618 d.label =
"eve_shadow_layout";
619 d.bindGroupLayoutCount = 1;
620 d.bindGroupLayouts = &shadowSetLayout;
621 return device.CreatePipelineLayout(&
d);
624wgpu::PipelineLayout Graphics::makeGbufferPipelineLayout() {
625 wgpu::PipelineLayoutDescriptor
d{};
626 d.label =
"eve_gbuffer_layout";
627 d.bindGroupLayoutCount = 1;
628 d.bindGroupLayouts = &gbufferSetLayout;
629 return device.CreatePipelineLayout(&
d);
632wgpu::PipelineLayout Graphics::makeDecalPipelineLayout() {
633 wgpu::PipelineLayoutDescriptor
d{};
634 d.label =
"eve_decal_layout";
635 d.bindGroupLayoutCount = 1;
636 d.bindGroupLayouts = &decalSetLayout;
637 return device.CreatePipelineLayout(&
d);
640wgpu::PipelineLayout Graphics::makeVoxelPipelineLayout() {
641 wgpu::PipelineLayoutDescriptor
d{};
642 d.label =
"eve_voxel_layout";
643 d.bindGroupLayoutCount = 1;
644 d.bindGroupLayouts = &voxelSetLayout;
645 return device.CreatePipelineLayout(&
d);
658WGPUShaderModuleDescriptor mdDesc(
const std::string&
code) {
659 static WGPUShaderSourceWGSL wd{};
660 wd.chain.sType = WGPUSType_ShaderSourceWGSL;
661 wd.code =
sv(
code.c_str());
662 WGPUShaderModuleDescriptor md{};
663 md.nextInChain = &wd.chain;
667void fillVertexLayout(WGPUVertexBufferLayout&
layout, uint64_t stride,
const WGPUVertexAttribute* attrs,
668 uint32_t attrCount) {
669 layout.arrayStride = stride;
670 layout.stepMode = WGPUVertexStepMode_Vertex;
671 layout.attributeCount = attrCount;
675WGPUBlendState alphaBlend() {
677 b.color.srcFactor = WGPUBlendFactor_SrcAlpha;
678 b.color.dstFactor = WGPUBlendFactor_OneMinusSrcAlpha;
679 b.color.operation = WGPUBlendOperation_Add;
680 b.alpha.srcFactor = WGPUBlendFactor_One;
681 b.alpha.dstFactor = WGPUBlendFactor_OneMinusSrcAlpha;
682 b.alpha.operation = WGPUBlendOperation_Add;
686WGPUBlendState noBlend() {
688 b.color.srcFactor = WGPUBlendFactor_One;
689 b.color.dstFactor = WGPUBlendFactor_Zero;
690 b.color.operation = WGPUBlendOperation_Add;
691 b.alpha.srcFactor = WGPUBlendFactor_One;
692 b.alpha.dstFactor = WGPUBlendFactor_Zero;
693 b.alpha.operation = WGPUBlendOperation_Add;
697WGPUBlendState additiveBlend() {
699 b.color.srcFactor = WGPUBlendFactor_SrcAlpha;
700 b.color.dstFactor = WGPUBlendFactor_One;
701 b.color.operation = WGPUBlendOperation_Add;
702 b.alpha.srcFactor = WGPUBlendFactor_One;
703 b.alpha.dstFactor = WGPUBlendFactor_One;
704 b.alpha.operation = WGPUBlendOperation_Add;
708WGPUBlendState premultipliedBlend() {
709 WGPUBlendState
b = alphaBlend();
710 b.color.srcFactor = WGPUBlendFactor_One;
714WGPUBlendState multiplyBlend() {
715 WGPUBlendState
b = alphaBlend();
716 b.color.srcFactor = WGPUBlendFactor_Dst;
724WGPUBlendState blendState(
BlendMode mode) {
731 default:
return alphaBlend();
739wgpu::RenderPipeline make2DColorPipeline(wgpu::Device& dev, WGPUTextureFormat format,
BlendMode mode) {
740 wgpu::VertexAttribute
attrs[2] = {};
741 attrs[0].format = wgpu::VertexFormat::Float32x2;
743 attrs[0].shaderLocation = 0;
744 attrs[1].format = wgpu::VertexFormat::Float32x4;
746 attrs[1].shaderLocation = 1;
752 b.vertexLayout(24, wgpu::VertexStepMode::Vertex, attrs, 2);
754 b.colorTarget(format, mode);
757 b.frontFace(wgpu::FrontFace::CW);
758 b.cull(wgpu::CullMode::Back);
762wgpu::RenderPipeline make2DTexturedPipeline(wgpu::Device& dev, wgpu::PipelineLayout
layout, WGPUTextureFormat format,
763 BlendMode mode,
bool toneMapScene =
false) {
764 WGPUVertexAttribute
attrs[3] = {};
765 attrs[0].format = WGPUVertexFormat_Float32x2;
767 attrs[0].shaderLocation = 0;
768 attrs[1].format = WGPUVertexFormat_Float32x4;
770 attrs[1].shaderLocation = 1;
771 attrs[2].format = WGPUVertexFormat_Float32x2;
772 attrs[2].offset = 24;
773 attrs[2].shaderLocation = 2;
774 WGPUVertexBufferLayout vb{};
775 fillVertexLayout(vb, 32, attrs, 3);
777 WGPUColorTargetState
target{};
779 target.writeMask = WGPUColorWriteMask_All;
780 WGPUBlendState bs = alphaBlend();
782 bs = additiveBlend();
784 bs = premultipliedBlend();
786 bs = multiplyBlend();
791 WGPURenderPipelineDescriptor pd{};
792 pd.label =
sv(
"eve_textured2d");
795 static constexpr const char* kSceneTonemapFragWgsl = R
"wgsl(
797 @location(0) color: vec4f,
798 @location(1) uv: vec2f,
800@group(0) @binding(0) var colorTex: texture_2d<f32>;
801@group(0) @binding(2) var colorSampler: sampler;
802@group(0) @binding(1) var rawSceneTex: texture_2d<f32>;
803@group(0) @binding(3) var rawSceneSampler: sampler;
804fn acesFitted(c0: vec3f) -> vec3f {
805 let c = max(c0, vec3f(0.0));
806 return clamp((c * (2.51 * c + vec3f(0.03))) /
807 (c * (2.43 * c + vec3f(0.59)) + vec3f(0.14)),
808 vec3f(0.0), vec3f(1.0));
810fn linearToSrgb(c: vec3f) -> vec3f {
812 let high = 1.055 * pow(max(c, vec3f(0.0)), vec3f(1.0 / 2.4)) - vec3f(0.055);
813 return select(low, high, c > vec3f(0.0031308));
815fn automaticExposure(packedLimits: f32, raw: bool) -> f32 {
816 var logLuminance: array<f32, 16>;
818 for (var y = 0; y < 4; y = y + 1) {
819 for (var x = 0; x < 4; x = x + 1) {
820 let uv = (vec2f(f32(x), f32(y)) + vec2f(0.5)) * 0.25;
821 var sampleColor = textureSampleLevel(colorTex, colorSampler, uv, 0.0).rgb;
823 sampleColor = textureSampleLevel(rawSceneTex, rawSceneSampler, uv, 0.0).rgb;
825 sampleColor = max(sampleColor, vec3f(0.0));
826 let luminance = dot(sampleColor, vec3f(0.2126, 0.7152, 0.0722));
827 logLuminance[sampleIndex] = log2(clamp(luminance, 0.0001, 65504.0));
831 for (var i = 1; i < 16; i = i + 1) {
832 let value = logLuminance[i];
834 while (j >= 0 && logLuminance[j] > value) {
835 logLuminance[j + 1] = logLuminance[j];
838 logLuminance[j + 1] = value;
840 var trimmedLogSum = 0.0;
841 for (var i = 2; i < 14; i = i + 1) {
842 trimmedLogSum += logLuminance[i];
844 let geometricMean = exp2(trimmedLogSum * (1.0 / 12.0));
845 let packed = round(packedLimits);
846 let minEV = (packed - floor(packed * (1.0 / 256.0)) * 256.0) *
848 let maxEV = floor(packed * (1.0 / 256.0)) * (1.0 / 8.0) - 16.0;
849 return clamp(0.18 / max(geometricMean, 0.0001), exp2(minEV), exp2(maxEV));
851fn bloomPrefilter(color: vec3f, threshold: f32) -> vec3f {
852 let brightness = max(color.r, max(color.g, color.b));
853 let knee = max(threshold * 0.1, 0.01);
854 let soft = clamp((brightness - threshold + knee) / (2.0 * knee), 0.0, 1.0);
855 let contribution = max(brightness - threshold, soft * soft * knee);
856 return color * (contribution / max(brightness, 0.0001));
858fn sampleBloom(uv: vec2f, threshold: f32) -> vec3f {
859 let texel = 1.0 / vec2f(textureDimensions(colorTex));
860 var bloom = bloomPrefilter(textureSampleLevel(colorTex, colorSampler, uv, 0.0).rgb,
862 let offsets = array<vec2f, 12>(
863 vec2f(2.00, 0.00), vec2f(-1.47, 1.35), vec2f(0.17, -1.99), vec2f(1.22, 1.58),
864 vec2f(4.22, -2.68), vec2f(-4.92, -0.87), vec2f(3.04, 3.97), vec2f(-0.44, -4.98),
865 vec2f(-5.28, 7.28), vec2f(8.89, -1.41), vec2f(-7.82, -4.46), vec2f(2.07, 8.76));
866 let weights = array<f32, 12>(
867 0.10, 0.10, 0.10, 0.10,
868 0.06, 0.06, 0.06, 0.06,
869 0.04, 0.04, 0.04, 0.04);
870 for (var q = 0; q < 12; q = q + 1) {
871 bloom += bloomPrefilter(textureSampleLevel(colorTex, colorSampler,
872 uv + offsets[q] * texel, 0.0).rgb, threshold) * weights[q];
877fn fs_main(in: FSIn) -> @location(0) vec4f {
878 let hdr = textureSample(colorTex, colorSampler, in.uv);
879 let exposure = 1.0; // Applied once by the dedicated HDR pre-exposure pass.
880 let encodeSrgb = in.color.a >= 65536.0;
881 let bloomPacked = round(in.color.a) - floor(round(in.color.a) * (1.0 / 65536.0)) * 65536.0;
882 let bloomIntensity = (bloomPacked - floor(bloomPacked * (1.0 / 256.0)) * 256.0) *
884 let bloomThreshold = floor(bloomPacked * (1.0 / 256.0)) * (1.0 / 16.0);
885 var bloom = vec3f(0.0);
886 if (bloomIntensity > 0.0) {
887 bloom = sampleBloom(in.uv, bloomThreshold);
889 var displayColor = acesFitted((hdr.rgb + bloom * bloomIntensity) * exposure);
891 displayColor = linearToSrgb(displayColor);
893 return vec4f(displayColor, hdr.a);
897 pd.vertex.module = vertModule.Get();
898 pd.vertex.entryPoint = sv("vs_main");
899 pd.vertex.bufferCount = 1;
900 pd.vertex.buffers = &vb;
901 WGPUFragmentState fs{};
902 fs.module = fragModule.Get();
903 fs.entryPoint =
sv(
"fs_main");
907 pd.primitive.topology = WGPUPrimitiveTopology_TriangleList;
908 pd.primitive.frontFace = WGPUFrontFace_CCW;
909 pd.primitive.cullMode = WGPUCullMode_None;
910 pd.primitive.stripIndexFormat = WGPUIndexFormat_Undefined;
911 pd.depthStencil =
nullptr;
912 pd.multisample.count = 1;
915 pd.multisample.mask = 0xFFFFFFFFu;
916 return dev.CreateRenderPipeline(
reinterpret_cast<const wgpu::RenderPipelineDescriptor*
>(&pd));
919wgpu::RenderPipeline make2DLitPipeline(wgpu::Device& dev, wgpu::PipelineLayout
layout, WGPUTextureFormat format,
921 wgpu::VertexAttribute
attrs[3] = {};
922 attrs[0].format = wgpu::VertexFormat::Float32x2;
924 attrs[0].shaderLocation = 0;
925 attrs[1].format = wgpu::VertexFormat::Float32x4;
927 attrs[1].shaderLocation = 1;
928 attrs[2].format = wgpu::VertexFormat::Float32x2;
929 attrs[2].offset = 24;
930 attrs[2].shaderLocation = 2;
933 b.vertexLayout(32, wgpu::VertexStepMode::Vertex, attrs, 3);
936 b.colorTarget(format,
blend);
942void Graphics::create2DPipelines() {
943 tex2DSetLayout = make2DBindGroupLayout();
944 tex2DPipelineLayout = make2DPipelineLayout();
946 colorPipeline = make2DColorPipeline(device, surfaceFormat,
BlendMode::Alpha);
947 texturedPipeline = make2DTexturedPipeline(device, tex2DPipelineLayout, surfaceFormat,
BlendMode::Alpha);
949 texturedAdditivePipeline = make2DTexturedPipeline(device, tex2DPipelineLayout, surfaceFormat,
BlendMode::Additive);
951 texturedPremultipliedPipeline =
954 texturedMultiplyPipeline = make2DTexturedPipeline(device, tex2DPipelineLayout, surfaceFormat,
BlendMode::Multiply);
955 colorOpaquePipeline = make2DColorPipeline(device, surfaceFormat,
BlendMode::Opaque);
956 texturedOpaquePipeline = make2DTexturedPipeline(device, tex2DPipelineLayout, surfaceFormat,
BlendMode::Opaque);
957 sceneTonemapPipeline = make2DTexturedPipeline(device, tex2DPipelineLayout, surfaceFormat,
BlendMode::Opaque,
true);
958 lit2dPipeline = make2DLitPipeline(device, tex2DPipelineLayout, surfaceFormat,
BlendMode::Alpha);
959 lit2dAdditivePipeline = make2DLitPipeline(device, tex2DPipelineLayout, surfaceFormat,
BlendMode::Additive);
960 lit2dPremultipliedPipeline =
962 lit2dMultiplyPipeline = make2DLitPipeline(device, tex2DPipelineLayout, surfaceFormat,
BlendMode::Multiply);
963 lit2dOpaquePipeline = make2DLitPipeline(device, tex2DPipelineLayout, surfaceFormat,
BlendMode::Opaque);
965 offscreenColorPipeline = make2DColorPipeline(device, WGPUTextureFormat_RGBA8Unorm,
BlendMode::Alpha);
966 offscreenTexturedPipeline =
967 make2DTexturedPipeline(device, tex2DPipelineLayout, WGPUTextureFormat_RGBA8Unorm,
BlendMode::Alpha);
968 offscreenColorAdditivePipeline = make2DColorPipeline(device, WGPUTextureFormat_RGBA8Unorm,
BlendMode::Additive);
969 offscreenTexturedAdditivePipeline =
970 make2DTexturedPipeline(device, tex2DPipelineLayout, WGPUTextureFormat_RGBA8Unorm,
BlendMode::Additive);
971 offscreenColorPremultipliedPipeline =
973 offscreenTexturedPremultipliedPipeline =
975 offscreenColorMultiplyPipeline = make2DColorPipeline(device, WGPUTextureFormat_RGBA8Unorm,
BlendMode::Multiply);
976 offscreenTexturedMultiplyPipeline =
977 make2DTexturedPipeline(device, tex2DPipelineLayout, WGPUTextureFormat_RGBA8Unorm,
BlendMode::Multiply);
978 offscreenColorOpaquePipeline = make2DColorPipeline(device, WGPUTextureFormat_RGBA8Unorm,
BlendMode::Opaque);
979 offscreenTexturedOpaquePipeline =
980 make2DTexturedPipeline(device, tex2DPipelineLayout, WGPUTextureFormat_RGBA8Unorm,
BlendMode::Opaque);
981 hdrOffscreenTexturedPipeline =
982 make2DTexturedPipeline(device, tex2DPipelineLayout, WGPUTextureFormat_RGBA16Float,
BlendMode::Alpha);
983 hdrOffscreenTexturedOpaquePipeline =
984 make2DTexturedPipeline(device, tex2DPipelineLayout, WGPUTextureFormat_RGBA16Float,
BlendMode::Opaque);
985 offscreenLitPipeline =
986 make2DLitPipeline(device, tex2DPipelineLayout, WGPUTextureFormat_RGBA8Unorm,
BlendMode::Alpha);
987 offscreenLitAdditivePipeline =
988 make2DLitPipeline(device, tex2DPipelineLayout, WGPUTextureFormat_RGBA8Unorm,
BlendMode::Additive);
989 offscreenLitPremultipliedPipeline =
991 offscreenLitMultiplyPipeline =
992 make2DLitPipeline(device, tex2DPipelineLayout, WGPUTextureFormat_RGBA8Unorm,
BlendMode::Multiply);
993 offscreenLitOpaquePipeline =
994 make2DLitPipeline(device, tex2DPipelineLayout, WGPUTextureFormat_RGBA8Unorm,
BlendMode::Opaque);
995 hdrOffscreenLitPipeline =
996 make2DLitPipeline(device, tex2DPipelineLayout, WGPUTextureFormat_RGBA16Float,
BlendMode::Alpha);
997 hdrOffscreenLitAdditivePipeline =
998 make2DLitPipeline(device, tex2DPipelineLayout, WGPUTextureFormat_RGBA16Float,
BlendMode::Additive);
999 hdrOffscreenLitPremultipliedPipeline =
1001 hdrOffscreenLitMultiplyPipeline =
1002 make2DLitPipeline(device, tex2DPipelineLayout, WGPUTextureFormat_RGBA16Float,
BlendMode::Multiply);
1003 hdrOffscreenLitOpaquePipeline =
1004 make2DLitPipeline(device, tex2DPipelineLayout, WGPUTextureFormat_RGBA16Float,
BlendMode::Opaque);
1007wgpu::RenderPipeline Graphics::get2DColorPipeline(
BlendMode blend,
bool offscreen) {
1009 case BlendMode::Additive:
return offscreen ? offscreenColorAdditivePipeline : colorAdditivePipeline;
1011 return offscreen ? offscreenColorPremultipliedPipeline : colorPremultipliedPipeline;
1012 case BlendMode::Multiply:
return offscreen ? offscreenColorMultiplyPipeline : colorMultiplyPipeline;
1013 case BlendMode::Opaque:
return offscreen ? offscreenColorOpaquePipeline : colorOpaquePipeline;
1015 default:
return offscreen ? offscreenColorPipeline : colorPipeline;
1019wgpu::RenderPipeline Graphics::get2DTexturedPipeline(
BlendMode blend,
bool offscreen) {
1021 case BlendMode::Additive:
return offscreen ? offscreenTexturedAdditivePipeline : texturedAdditivePipeline;
1023 return offscreen ? offscreenTexturedPremultipliedPipeline : texturedPremultipliedPipeline;
1024 case BlendMode::Multiply:
return offscreen ? offscreenTexturedMultiplyPipeline : texturedMultiplyPipeline;
1025 case BlendMode::Opaque:
return offscreen ? offscreenTexturedOpaquePipeline : texturedOpaquePipeline;
1027 default:
return offscreen ? offscreenTexturedPipeline : texturedPipeline;
1031wgpu::RenderPipeline Graphics::get2DLitPipeline(
BlendMode blend, WGPUTextureFormat format) {
1032 const bool hdr = format == WGPUTextureFormat_RGBA16Float;
1033 const bool offscreen = hdr || uint32_t(format) != uint32_t(surfaceFormat);
1034 auto pick = [](
BlendMode mode, wgpu::RenderPipeline alpha, wgpu::RenderPipeline additive,
1035 wgpu::RenderPipeline premultiplied, wgpu::RenderPipeline multiply,
1036 wgpu::RenderPipeline opaque) -> wgpu::RenderPipeline {
1043 default:
return alpha;
1047 return pick(
blend, hdrOffscreenLitPipeline, hdrOffscreenLitAdditivePipeline,
1048 hdrOffscreenLitPremultipliedPipeline, hdrOffscreenLitMultiplyPipeline,
1049 hdrOffscreenLitOpaquePipeline);
1052 return pick(
blend, offscreenLitPipeline, offscreenLitAdditivePipeline, offscreenLitPremultipliedPipeline,
1053 offscreenLitMultiplyPipeline, offscreenLitOpaquePipeline);
1055 return pick(
blend, lit2dPipeline, lit2dAdditivePipeline, lit2dPremultipliedPipeline, lit2dMultiplyPipeline,
1056 lit2dOpaquePipeline);
1059void Graphics::createMesh3DPipelines() {
1060 mesh3dSetLayout = makeMesh3DBindGroupLayout();
1062 clearMeshBindGroupCache();
1063 mesh3dPipelineLayout = makeMesh3DPipelineLayout();
1067 bool canvasTarget) {
1069 wgpu::RenderPipeline& pipeline = canvasTarget ? mesh3dCanvasPipelines[
index] : mesh3dPipelines[
index];
1070 if (pipeline)
return pipeline;
1072 WGPUVertexAttribute
attrs[6] = {};
1074 WGPUVertexBufferLayout vb{};
1075 fillVertexLayout(vb,
sizeof(MeshVertex), attrs, 6);
1077 WGPUDepthStencilState ds{};
1078 ds.format = WGPUTextureFormat_Depth32Float;
1079 ds.depthWriteEnabled = depthWrite ? WGPUOptionalBool_True : WGPUOptionalBool_False;
1080 ds.depthCompare = WGPUCompareFunction_Less;
1082 WGPUBlendState blendDesc = blendState(
blend);
1083 WGPUColorTargetState
target{};
1084 target.format = sceneColorFormat;
1086 target.writeMask = WGPUColorWriteMask_All;
1090 WGPUFragmentState fs{};
1091 fs.module = fragModule.Get();
1092 fs.entryPoint =
sv(
"fs_main");
1096 WGPURenderPipelineDescriptor pd{};
1097 pd.label = canvasTarget ?
sv(
"eve_mesh3d_canvas_surface") :
sv(
"eve_mesh3d_surface");
1098 pd.layout = mesh3dPipelineLayout.Get();
1099 pd.vertex.module = vertModule.Get();
1100 pd.vertex.entryPoint =
sv(
"vs_main");
1101 pd.vertex.bufferCount = 1;
1102 pd.vertex.buffers = &vb;
1104 pd.primitive.topology = WGPUPrimitiveTopology_TriangleList;
1105 pd.primitive.frontFace = WGPUFrontFace_CCW;
1106 pd.primitive.cullMode =
doubleSided ? WGPUCullMode_None : WGPUCullMode_Back;
1107 pd.primitive.stripIndexFormat = WGPUIndexFormat_Undefined;
1108 pd.depthStencil = &ds;
1109 pd.multisample.count = canvasTarget ? 1 : sceneColorSamples;
1110 pd.multisample.mask = 0xFFFFFFFFu;
1111 pipeline = device.CreateRenderPipeline(
reinterpret_cast<const wgpu::RenderPipelineDescriptor*
>(&pd));
1120void Graphics::createMesh3DClusteredPipeline() {
1121 mesh3dClusteredSetLayout = makeMesh3DClusteredBindGroupLayout();
1122 mesh3dClusteredPipelineLayout = makeMesh3DClusteredPipelineLayout();
1124 WGPUVertexAttribute
attrs[6] = {};
1126 attrs[0].format = WGPUVertexFormat_Float32x3;
1127 attrs[0].offset = 0;
1128 attrs[0].shaderLocation = 0;
1129 attrs[1].format = WGPUVertexFormat_Float32x3;
1130 attrs[1].offset = 12;
1131 attrs[1].shaderLocation = 1;
1132 attrs[2].format = WGPUVertexFormat_Float32x2;
1133 attrs[2].offset = 24;
1134 attrs[2].shaderLocation = 2;
1135 WGPUVertexBufferLayout vb{};
1136 fillVertexLayout(vb,
sizeof(MeshVertex), attrs, 6);
1138 WGPUDepthStencilState ds{};
1139 ds.format = WGPUTextureFormat_Depth32Float;
1140 ds.depthWriteEnabled = WGPUOptionalBool_True;
1141 ds.depthCompare = WGPUCompareFunction_Less;
1142 ds.stencilReadMask = 0;
1143 ds.stencilWriteMask = 0;
1145 WGPUColorTargetState
target{};
1146 target.format = sceneColorFormat;
1148 target.writeMask = WGPUColorWriteMask_All;
1150 WGPURenderPipelineDescriptor pd{};
1151 pd.label =
sv(
"eve_mesh3d_clustered");
1152 pd.layout = mesh3dClusteredPipelineLayout.Get();
1155 pd.vertex.module = vertModule.Get();
1156 pd.vertex.entryPoint =
sv(
"vs_main");
1157 pd.vertex.bufferCount = 1;
1158 pd.vertex.buffers = &vb;
1159 WGPUFragmentState fs{};
1160 fs.module = fragModule.Get();
1161 fs.entryPoint =
sv(
"fs_main");
1165 pd.primitive.topology = WGPUPrimitiveTopology_TriangleList;
1166 pd.primitive.frontFace = WGPUFrontFace_CCW;
1167 pd.primitive.cullMode = WGPUCullMode_Back;
1168 pd.primitive.stripIndexFormat = WGPUIndexFormat_Undefined;
1169 pd.depthStencil = &ds;
1170 pd.multisample.count = sceneColorSamples;
1171 pd.multisample.mask = 0xFFFFFFFFu;
1172 mesh3dClusteredPipeline = device.CreateRenderPipeline(
reinterpret_cast<const wgpu::RenderPipelineDescriptor*
>(&pd));
1175void Graphics::createShadowPipelines() {
1176 shadowSetLayout = makeShadowBindGroupLayout();
1177 shadowPipelineLayout = makeShadowPipelineLayout();
1179 WGPUVertexAttribute
attrs[6] = {};
1181 attrs[0].format = WGPUVertexFormat_Float32x3;
1182 attrs[0].offset = 0;
1183 attrs[0].shaderLocation = 0;
1184 attrs[1].format = WGPUVertexFormat_Float32x3;
1185 attrs[1].offset = 12;
1186 attrs[1].shaderLocation = 1;
1187 attrs[2].format = WGPUVertexFormat_Float32x2;
1188 attrs[2].offset = 24;
1189 attrs[2].shaderLocation = 2;
1190 WGPUVertexBufferLayout vb{};
1191 fillVertexLayout(vb,
sizeof(MeshVertex), attrs, 6);
1193 WGPUDepthStencilState ds{};
1194 ds.format = WGPUTextureFormat_Depth32Float;
1195 ds.depthWriteEnabled = WGPUOptionalBool_True;
1196 ds.depthCompare = WGPUCompareFunction_Less;
1197 ds.stencilReadMask = 0;
1198 ds.stencilWriteMask = 0;
1200 WGPURenderPipelineDescriptor pd{};
1201 pd.label =
sv(
"eve_shadow");
1202 pd.layout = shadowPipelineLayout.Get();
1204 pd.vertex.module = vertModule.Get();
1205 pd.vertex.entryPoint =
sv(
"vs_main");
1206 pd.vertex.bufferCount = 1;
1207 pd.vertex.buffers = &vb;
1208 pd.fragment =
nullptr;
1209 pd.primitive.topology = WGPUPrimitiveTopology_TriangleList;
1210 pd.primitive.frontFace = WGPUFrontFace_CCW;
1211 pd.primitive.cullMode = WGPUCullMode_None;
1212 pd.primitive.stripIndexFormat = WGPUIndexFormat_Undefined;
1213 pd.depthStencil = &ds;
1214 pd.multisample.count = 1;
1217 pd.multisample.mask = 0xFFFFFFFFu;
1218 mesh3dShadowPipeline = device.CreateRenderPipeline(
reinterpret_cast<const wgpu::RenderPipelineDescriptor*
>(&pd));
1219 pd.label =
sv(
"eve_shadow_single_sided");
1220 pd.primitive.cullMode = WGPUCullMode_Back;
1221 mesh3dShadowSingleSidedPipeline =
1222 device.CreateRenderPipeline(
reinterpret_cast<const wgpu::RenderPipelineDescriptor*
>(&pd));
1226 pd.label =
sv(
"eve_shadow_alpha");
1227 pd.primitive.cullMode = WGPUCullMode_None;
1228 pd.vertex.module = alphaVertModule.Get();
1229 WGPUFragmentState alphaFs{};
1230 alphaFs.module = alphaFragModule.Get();
1231 alphaFs.entryPoint =
sv(
"fs_main");
1232 alphaFs.targetCount = 0;
1233 alphaFs.targets =
nullptr;
1234 pd.fragment = &alphaFs;
1235 mesh3dShadowAlphaPipeline =
1236 device.CreateRenderPipeline(
reinterpret_cast<const wgpu::RenderPipelineDescriptor*
>(&pd));
1237 pd.label =
sv(
"eve_shadow_alpha_single_sided");
1238 pd.primitive.cullMode = WGPUCullMode_Back;
1239 mesh3dShadowAlphaSingleSidedPipeline =
1240 device.CreateRenderPipeline(
reinterpret_cast<const wgpu::RenderPipelineDescriptor*
>(&pd));
1243void Graphics::createGbufferPipelines() {
1244 gbufferSetLayout = makeGbufferBindGroupLayout();
1245 gbufferPipelineLayout = makeGbufferPipelineLayout();
1247 WGPUVertexAttribute
attrs[6] = {};
1249 attrs[0].format = WGPUVertexFormat_Float32x3;
1250 attrs[0].offset = 0;
1251 attrs[0].shaderLocation = 0;
1252 attrs[1].format = WGPUVertexFormat_Float32x3;
1253 attrs[1].offset = 12;
1254 attrs[1].shaderLocation = 1;
1255 attrs[2].format = WGPUVertexFormat_Float32x2;
1256 attrs[2].offset = 24;
1257 attrs[2].shaderLocation = 2;
1258 WGPUVertexBufferLayout vb{};
1259 fillVertexLayout(vb,
sizeof(MeshVertex), attrs, 6);
1261 WGPUDepthStencilState ds{};
1262 ds.format = WGPUTextureFormat_Depth32Float;
1263 ds.depthWriteEnabled = WGPUOptionalBool_True;
1264 ds.depthCompare = WGPUCompareFunction_Less;
1265 ds.stencilReadMask = 0;
1266 ds.stencilWriteMask = 0;
1268 WGPUColorTargetState targets[5] = {};
1269 for (
int i = 0; i < 5; ++i) {
1270 targets[i].format = WGPUTextureFormat_RGBA8Unorm;
1271 targets[i].blend =
nullptr;
1272 targets[i].writeMask = WGPUColorWriteMask_All;
1275 WGPURenderPipelineDescriptor pd{};
1276 pd.label =
sv(
"eve_gbuffer");
1277 pd.layout = gbufferPipelineLayout.Get();
1280 pd.vertex.module = vertModule.Get();
1281 pd.vertex.entryPoint =
sv(
"vs_main");
1282 pd.vertex.bufferCount = 1;
1283 pd.vertex.buffers = &vb;
1284 WGPUFragmentState fs{};
1285 fs.module = fragModule.Get();
1286 fs.entryPoint =
sv(
"fs_main");
1288 fs.targets = targets;
1290 pd.primitive.topology = WGPUPrimitiveTopology_TriangleList;
1291 pd.primitive.frontFace = WGPUFrontFace_CCW;
1292 pd.primitive.cullMode = WGPUCullMode_None;
1293 pd.primitive.stripIndexFormat = WGPUIndexFormat_Undefined;
1294 pd.depthStencil = &ds;
1295 pd.multisample.count = 1;
1298 pd.multisample.mask = 0xFFFFFFFFu;
1299 mesh3dGbufferPipeline = device.CreateRenderPipeline(
reinterpret_cast<const wgpu::RenderPipelineDescriptor*
>(&pd));
1302 pd.label =
sv(
"eve_gbuffer_alpha");
1303 fs.module = alphaFragModule.Get();
1304 mesh3dGbufferAlphaPipeline =
1305 device.CreateRenderPipeline(
reinterpret_cast<const wgpu::RenderPipelineDescriptor*
>(&pd));
1308void Graphics::createDecalPipeline() {
1309 decalSetLayout = makeDecalBindGroupLayout();
1310 decalPipelineLayout = makeDecalPipelineLayout();
1313 WGPUBlendState
blend = alphaBlend();
1314 WGPUColorTargetState targets[3]{};
1315 for (
auto&
target : targets) {
1316 target.format = WGPUTextureFormat_RGBA8Unorm;
1318 target.writeMask = WGPUColorWriteMask_All;
1320 WGPUFragmentState fs{};
1321 fs.module =
frag.Get();
1322 fs.entryPoint =
sv(
"fs_main");
1324 fs.targets = targets;
1325 WGPURenderPipelineDescriptor pd{};
1326 pd.label =
sv(
"eve_decal");
1327 pd.layout = decalPipelineLayout.Get();
1328 pd.vertex.module =
vert.Get();
1329 pd.vertex.entryPoint =
sv(
"vs_main");
1331 pd.primitive.topology = WGPUPrimitiveTopology_TriangleList;
1334 pd.primitive.frontFace = WGPUFrontFace_CW;
1335 pd.primitive.cullMode = WGPUCullMode_None;
1336 pd.multisample.count = 1;
1337 pd.multisample.mask = 0xFFFFFFFFu;
1338 decalPipeline = device.CreateRenderPipeline(
reinterpret_cast<const wgpu::RenderPipelineDescriptor*
>(&pd));
1346void Graphics::ensureAOResources(
int width,
int height) {
1349 BindGroupLayoutBuilder
b;
1350 b.buffer(0, wgpu::ShaderStage::Fragment, wgpu::BufferBindingType::Uniform,
false, 32);
1351 b.texture(1, wgpu::ShaderStage::Fragment, wgpu::TextureSampleType::Depth, wgpu::TextureViewDimension::e2D);
1352 b.sampler(2, wgpu::ShaderStage::Fragment, wgpu::SamplerBindingType::Filtering);
1353 aoSetLayout =
b.build(device,
"eve_ao");
1354 WGPUBindGroupLayout layouts[1] = {aoSetLayout.Get()};
1355 WGPUPipelineLayoutDescriptor pl{};
1356 pl.label =
sv(
"eve_ao_layout");
1357 pl.bindGroupLayoutCount = 1;
1358 pl.bindGroupLayouts = layouts;
1359 aoPipelineLayout = device.CreatePipelineLayout(
reinterpret_cast<const wgpu::PipelineLayoutDescriptor*
>(&pl));
1361 WGPUVertexAttribute
attrs[3]{};
1362 attrs[0].format = WGPUVertexFormat_Float32x2;
1363 attrs[0].offset = 0;
1364 attrs[0].shaderLocation = 0;
1365 attrs[1].format = WGPUVertexFormat_Float32x4;
1366 attrs[1].offset = 8;
1367 attrs[1].shaderLocation = 1;
1368 attrs[2].format = WGPUVertexFormat_Float32x2;
1369 attrs[2].offset = 24;
1370 attrs[2].shaderLocation = 2;
1371 WGPUVertexBufferLayout vb{};
1372 fillVertexLayout(vb, 32, attrs, 3);
1374 WGPUColorTargetState
target{};
1375 target.format = WGPUTextureFormat_RGBA8Unorm;
1377 target.writeMask = WGPUColorWriteMask_All;
1379 WGPURenderPipelineDescriptor pd{};
1380 pd.label =
sv(
"eve_ssao");
1381 pd.layout = aoPipelineLayout.Get();
1384 pd.vertex.module = vertModule.Get();
1385 pd.vertex.entryPoint =
sv(
"vs_main");
1386 pd.vertex.bufferCount = 1;
1387 pd.vertex.buffers = &vb;
1388 WGPUFragmentState fs{};
1389 fs.module = fragModule.Get();
1390 fs.entryPoint =
sv(
"fs_main");
1394 pd.primitive.topology = WGPUPrimitiveTopology_TriangleList;
1395 pd.primitive.frontFace = WGPUFrontFace_CCW;
1396 pd.primitive.cullMode = WGPUCullMode_None;
1397 pd.primitive.stripIndexFormat = WGPUIndexFormat_Undefined;
1398 pd.multisample.count = 1;
1399 pd.multisample.mask = 0xFFFFFFFFu;
1400 aoPipeline = device.CreateRenderPipeline(
reinterpret_cast<const wgpu::RenderPipelineDescriptor*
>(&pd));
1403 WGPUBufferDescriptor bd{};
1404 bd.label =
sv(
"eve_ao_ubo");
1406 bd.usage = WGPUBufferUsage_CopyDst | WGPUBufferUsage_Uniform;
1407 bd.mappedAtCreation =
false;
1408 aoUbo = device.CreateBuffer(
reinterpret_cast<const wgpu::BufferDescriptor*
>(&bd));
1411 for (
int i = 0; i < 2; ++i) {
1412 WGPUTextureDescriptor td{};
1413 td.label =
sv(
"eve_ao");
1414 td.dimension = WGPUTextureDimension_2D;
1417 td.size = {
static_cast<uint32_t
>((
width + 1) / 2),
static_cast<uint32_t
>((
height + 1) / 2), 1};
1419 td.format = WGPUTextureFormat_RGBA8Unorm;
1420 td.mipLevelCount = 1;
1421 td.usage = WGPUTextureUsage_TextureBinding | WGPUTextureUsage_RenderAttachment;
1422 aoTex[i] = device.CreateTexture(
reinterpret_cast<const wgpu::TextureDescriptor*
>(&td));
1423 aoView[i] = aoTex[i].CreateView();
1427 clearMeshBindGroupCache();
1429 if (!fullscreenQuadReady) {
1431 -1.f, -1.f, 1.f, 1.f, 1.f, 1.f, 0.f, 0.f, 1.f, -1.f, 1.f, 1.f, 1.f, 1.f, 1.f, 0.f,
1432 1.f, 1.f, 1.f, 1.f, 1.f, 1.f, 1.f, 1.f, -1.f, 1.f, 1.f, 1.f, 1.f, 1.f, 0.f, 1.f,
1434 uint32_t
indices[6] = {0, 1, 2, 2, 3, 0};
1435 WGPUBufferDescriptor vbd{};
1436 vbd.label =
sv(
"eve_fullscreen_vb");
1437 vbd.size =
sizeof(
verts);
1438 vbd.usage = WGPUBufferUsage_CopyDst | WGPUBufferUsage_Vertex;
1439 fullscreenQuadVb = device.CreateBuffer(
reinterpret_cast<const wgpu::BufferDescriptor*
>(&vbd));
1440 queue.WriteBuffer(fullscreenQuadVb, 0,
verts,
sizeof(
verts));
1441 WGPUBufferDescriptor ibd{};
1442 ibd.label =
sv(
"eve_fullscreen_ib");
1444 ibd.usage = WGPUBufferUsage_CopyDst | WGPUBufferUsage_Index;
1445 fullscreenQuadIb = device.CreateBuffer(
reinterpret_cast<const wgpu::BufferDescriptor*
>(&ibd));
1446 queue.WriteBuffer(fullscreenQuadIb, 0,
indices,
sizeof(
indices));
1447 fullscreenQuadReady =
true;
1451wgpu::BindGroup Graphics::makeAOBindGroup(wgpu::TextureView depthView) {
1452 WGPUBindGroupEntry
entries[3]{};
1454 entries[0].buffer = aoUbo.Get();
1457 entries[1].textureView = depthView.Get();
1459 entries[2].sampler = mainSampler.Get();
1460 WGPUBindGroupDescriptor
desc{};
1461 desc.label =
sv(
"eve_ao_group");
1462 desc.layout = aoSetLayout.Get();
1463 desc.entryCount = 3;
1465 return device.CreateBindGroup(
reinterpret_cast<const wgpu::BindGroupDescriptor*
>(&
desc));
1472uint32_t Graphics::UboArena::alloc(uint64_t
size, uint64_t alignment) {
1473 uint64_t aligned = (used + alignment - 1) / alignment * alignment;
1474 used = aligned +
size;
1475 return static_cast<uint32_t
>(aligned);
1478uint64_t Graphics::VertexArena::alloc(uint64_t
bytes) {
1484Graphics::UboArena& Graphics::currentUboArena() {
return uboArenas[currentFrameSlot()]; }
1485Graphics::VertexArena& Graphics::currentVertexArena() {
return vertexArenas[currentFrameSlot()]; }
1487void Graphics::ensureUboArena(UboArena& arena, uint64_t
bytes) {
1488 if (arena.buffer && arena.capacity >=
bytes)
return;
1489 uint64_t
size = arena.capacity;
1491 WGPUBufferDescriptor bd{};
1492 bd.label =
sv(
"eve_ubo_arena");
1494 bd.usage = WGPUBufferUsage_CopyDst | WGPUBufferUsage_Uniform;
1495 bd.mappedAtCreation =
false;
1496 arena.buffer = device.CreateBuffer(
reinterpret_cast<const wgpu::BufferDescriptor*
>(&bd));
1497 arena.capacity =
size;
1501void Graphics::ensureVertexArena(VertexArena& arena, uint64_t
bytes) {
1502 if (arena.buffer && arena.capacity >=
bytes)
return;
1503 uint64_t
size = arena.capacity;
1505 WGPUBufferDescriptor bd{};
1506 bd.label =
sv(
"eve_vertex_arena");
1508 bd.usage = WGPUBufferUsage_CopyDst | WGPUBufferUsage_Vertex;
1509 bd.mappedAtCreation =
false;
1510 arena.buffer = device.CreateBuffer(
reinterpret_cast<const wgpu::BufferDescriptor*
>(&bd));
1511 arena.capacity =
size;
1519wgpu::Sampler Graphics::makeSampler(
const TextureSampler&
s, uint32_t mipLevels)
const {
1520 WGPUSamplerDescriptor
d{};
1521 d.label =
sv(
"eve_sampler");
1522 d.addressModeU =
s.repeatU ? WGPUAddressMode_Repeat : WGPUAddressMode_ClampToEdge;
1523 d.addressModeV =
s.repeatV ? WGPUAddressMode_Repeat : WGPUAddressMode_ClampToEdge;
1524 d.addressModeW =
s.repeatW ? WGPUAddressMode_Repeat : WGPUAddressMode_ClampToEdge;
1528 d.mipmapFilter = WGPUMipmapFilterMode_Nearest;
1529 d.lodMinClamp = 0.f;
1530 d.lodMaxClamp = 0.f;
1532 d.mipmapFilter =
s.mipmap ==
MipmapMode::Nearest ? WGPUMipmapFilterMode_Nearest : WGPUMipmapFilterMode_Linear;
1533 d.lodMinClamp =
s.minLod;
1534 d.lodMaxClamp = std::min(std::max(
s.maxLod, 0.f),
float(mipLevels - 1));
1535 if (
d.lodMaxClamp <
d.lodMinClamp)
d.lodMaxClamp =
d.lodMinClamp;
1537 float aniso =
s.maxAnisotropy > 1.f ?
s.maxAnisotropy : 1.f;
1538 d.maxAnisotropy = std::min(std::max(aniso, 1.f), std::max(maxSamplerAnisotropy, 1.f));
1539 return device.CreateSampler(
reinterpret_cast<const wgpu::SamplerDescriptor*
>(&
d));
1554 if (!data)
throw Exception(
"newTexture: null ImageData");
1556 return newTexture(data->getWidth(), data->getHeight(),
static_cast<const uint8_t*
>(data->getData()),
info);
1560 if (!data)
throw Exception(
"newTexture: null ImageData");
1561 if (data->getFormat() !=
"RGBA8")
throw Exception(
"newTexture: only RGBA8 supported");
1562 return newTexture(data->getWidth(), data->getHeight(),
static_cast<const uint8_t*
>(data->getData()),
info);
1566 if (!data || contentKey.empty() || data->getFormat() !=
"RGBA8")
1568 "RGBA8 image and nonempty content key are required", {},
1569 {},
"graphics.texture.shared"));
1570 if (
auto found = sharedTexturesByContent.find(contentKey);
found != sharedTexturesByContent.end())
1574 if (!texture)
throw Exception(
"shared texture upload produced no texture");
1575 sharedTexturesByContent.emplace(contentKey, texture);
1577 }
catch (
const std::exception&
error) {
1588 if (
info.sampler.maxAnisotropy < 1.f)
info.sampler.maxAnisotropy = 1.f;
1590 auto gpu = std::make_unique<GpuTexture>();
1593 gpu->samplerState =
info.sampler;
1596 WGPUTextureDescriptor td{};
1597 td.label =
sv(
"eve_tex2d");
1598 td.dimension = WGPUTextureDimension_2D;
1599 td.size.width =
static_cast<uint32_t
>(
width);
1600 td.size.height =
static_cast<uint32_t
>(
height);
1601 td.size.depthOrArrayLayers = 1;
1603 td.format = WGPUTextureFormat_RGBA8Unorm;
1604 td.mipLevelCount = gpu->mipLevels;
1605 td.usage = WGPUTextureUsage_TextureBinding | WGPUTextureUsage_CopyDst | WGPUTextureUsage_RenderAttachment;
1606 gpu->texture = device.CreateTexture(
reinterpret_cast<const wgpu::TextureDescriptor*
>(&td));
1608 uploadTexturePixelsMips(gpu.get(), rgba,
width,
height);
1610 WGPUTextureViewDescriptor vd{};
1611 vd.format = WGPUTextureFormat_RGBA8Unorm;
1612 vd.dimension = WGPUTextureViewDimension_2D;
1613 vd.baseMipLevel = 0;
1614 vd.mipLevelCount = gpu->mipLevels;
1615 vd.baseArrayLayer = 0;
1616 vd.arrayLayerCount = 1;
1617 gpu->view = gpu->texture.CreateView(
reinterpret_cast<const wgpu::TextureViewDescriptor*
>(&vd));
1618 gpu->sampler = makeSampler(
info.sampler, gpu->mipLevels);
1623 tex->mipmapCount = int(gpu->mipLevels);
1624 tex->sampler =
info.sampler;
1625 tex->gpuHandle = gpu.get();
1627 ownedGpuTextures.push_back(std::move(gpu));
1628 ownedTextures.push_back(std::unique_ptr<Texture>(tex));
1634 uploadTexturePixelsMips(gt, rgba,
w,
h);
1637void Graphics::uploadTexturePixelsMips(GpuTexture* gt,
const uint8_t* rgba,
int w,
int h) {
1640 WGPUTexelCopyBufferLayout
layout{};
1642 layout.bytesPerRow =
static_cast<uint32_t
>(
w * 4);
1643 layout.rowsPerImage =
static_cast<uint32_t
>(
h);
1644 WGPUExtent3D extent{
static_cast<uint32_t
>(
w),
static_cast<uint32_t
>(
h), 1};
1646 for (uint32_t
m = 0;
m < gt->mipLevels; ++
m) {
1647 WGPUTexelCopyTextureInfo dst{};
1648 dst.texture = gt->texture.Get();
1650 dst.aspect = WGPUTextureAspect_All;
1651 queue.WriteTexture(
reinterpret_cast<const wgpu::TexelCopyTextureInfo*
>(&dst), rgba,
1652 static_cast<uint64_t
>(
w) *
h * 4,
1653 reinterpret_cast<const wgpu::TexelCopyBufferLayout*
>(&
layout),
1654 reinterpret_cast<const wgpu::Extent3D*
>(&extent));
1655 if (
m + 1 < gt->mipLevels) {
1657 const int nextW = std::max(
w / 2, 1);
1658 const int nextH = std::max(
h / 2, 1);
1659 std::vector<uint8_t>
next(
size_t(nextW) *
size_t(nextH) * 4u);
1660 for (
int y = 0;
y < nextH; ++
y) {
1661 const int y0 = std::min(
y * 2,
h - 1);
1662 const int y1 = std::min(y0 + 1,
h - 1);
1663 for (
int x = 0;
x < nextW; ++
x) {
1664 const int x0 = std::min(
x * 2,
w - 1);
1665 const int x1 = std::min(x0 + 1,
w - 1);
1666 for (
int c = 0;
c < 4; ++
c) {
1668 acc += rgba[(size_t(y0) *
w + x0) * 4u +
c];
1669 acc += rgba[(size_t(y0) *
w + x1) * 4u +
c];
1670 acc += rgba[(size_t(y1) *
w + x0) * 4u +
c];
1671 acc += rgba[(size_t(y1) *
w + x1) * 4u +
c];
1672 next[(size_t(
y) * nextW +
x) * 4u +
c] = uint8_t((acc + 2u) / 4u);
1680 layout.bytesPerRow =
static_cast<uint32_t
>(
w * 4);
1681 layout.rowsPerImage =
static_cast<uint32_t
>(
h);
1682 extent = {
static_cast<uint32_t
>(
w),
static_cast<uint32_t
>(
h), 1};
1693 if (faceSize <= 0 || !rgbaFaces)
throw Exception(
"newCubemap: invalid size or null data");
1697 if (
info.sampler.maxAnisotropy < 1.f)
info.sampler.maxAnisotropy = 1.f;
1699 auto gpu = std::make_unique<GpuTexture>();
1700 gpu->width = faceSize;
1701 gpu->height = faceSize;
1702 gpu->samplerState =
info.sampler;
1706 WGPUTextureDescriptor td{};
1707 td.label =
sv(
"eve_cubemap");
1708 td.dimension = WGPUTextureDimension_2D;
1709 td.size.width =
static_cast<uint32_t
>(faceSize);
1710 td.size.height =
static_cast<uint32_t
>(faceSize);
1711 td.size.depthOrArrayLayers = 6;
1713 td.format = WGPUTextureFormat_RGBA8Unorm;
1714 td.mipLevelCount = gpu->mipLevels;
1715 td.usage = WGPUTextureUsage_TextureBinding | WGPUTextureUsage_CopyDst;
1716 gpu->texture = device.CreateTexture(
reinterpret_cast<const wgpu::TextureDescriptor*
>(&td));
1719 size_t mipOffset = 0;
1720 int mipW = faceSize;
1721 int mipH = faceSize;
1722 for (uint32_t mip = 0; mip < gpu->mipLevels; ++mip) {
1723 const size_t mipFaceBytes = size_t(mipW) * mipH * 4u;
1724 for (
int f = 0;
f < 6; ++
f) {
1725 const uint8_t*
pixels = mipChain.data() + mipOffset + size_t(
f) * mipFaceBytes;
1726 WGPUTexelCopyBufferLayout
layout{};
1728 layout.bytesPerRow =
static_cast<uint32_t
>(mipW * 4);
1729 layout.rowsPerImage =
static_cast<uint32_t
>(mipH);
1730 WGPUExtent3D extent{
static_cast<uint32_t
>(mipW),
static_cast<uint32_t
>(mipH), 1};
1731 WGPUTexelCopyTextureInfo dst{};
1732 dst.texture = gpu->texture.Get();
1734 dst.aspect = WGPUTextureAspect_All;
1735 dst.origin = {0, 0,
static_cast<uint32_t
>(
f)};
1736 queue.WriteTexture(
reinterpret_cast<const wgpu::TexelCopyTextureInfo*
>(&dst),
pixels,
1737 static_cast<uint64_t
>(mipW) * mipH * 4,
1738 reinterpret_cast<const wgpu::TexelCopyBufferLayout*
>(&
layout),
1739 reinterpret_cast<const wgpu::Extent3D*
>(&extent));
1741 mipOffset += mipFaceBytes * 6u;
1742 mipW = std::max(mipW / 2, 1);
1743 mipH = std::max(mipH / 2, 1);
1746 WGPUTextureViewDescriptor vd{};
1747 vd.format = WGPUTextureFormat_RGBA8Unorm;
1748 vd.dimension = WGPUTextureViewDimension_Cube;
1749 vd.baseMipLevel = 0;
1750 vd.mipLevelCount = gpu->mipLevels;
1751 vd.baseArrayLayer = 0;
1752 vd.arrayLayerCount = 6;
1753 gpu->view = gpu->texture.CreateView(
reinterpret_cast<const wgpu::TextureViewDescriptor*
>(&vd));
1754 gpu->sampler = makeSampler(
info.sampler, gpu->mipLevels);
1757 tex->width = faceSize;
1758 tex->height = faceSize;
1759 tex->mipmapCount = int(gpu->mipLevels);
1760 tex->sampler =
info.sampler;
1761 tex->gpuHandle = gpu.get();
1763 ownedGpuTextures.push_back(std::move(gpu));
1764 ownedTextures.push_back(std::unique_ptr<Texture>(tex));
1769 if (faceSize <= 0)
throw Exception(
"newHDRCubemap: invalid face size");
1770 auto gpu = std::make_unique<GpuTexture>();
1771 gpu->width = faceSize;
1772 gpu->height = faceSize;
1779 WGPUTextureDescriptor td{};
1780 td.label =
sv(
"eve_hdr_reflection_probe_staging");
1781 td.dimension = WGPUTextureDimension_2D;
1782 td.size = {
static_cast<uint32_t
>(faceSize),
static_cast<uint32_t
>(faceSize), 6};
1784 td.format = WGPUTextureFormat_RGBA16Float;
1785 td.mipLevelCount = gpu->mipLevels;
1786 td.usage = WGPUTextureUsage_TextureBinding | WGPUTextureUsage_CopyDst | WGPUTextureUsage_CopySrc |
1787 WGPUTextureUsage_RenderAttachment;
1788 gpu->texture = device.CreateTexture(
reinterpret_cast<const wgpu::TextureDescriptor*
>(&td));
1790 WGPUTextureViewDescriptor vd{};
1791 vd.format = WGPUTextureFormat_RGBA16Float;
1792 vd.dimension = WGPUTextureViewDimension_Cube;
1793 vd.baseMipLevel = 0;
1794 vd.mipLevelCount = gpu->mipLevels;
1795 vd.baseArrayLayer = 0;
1796 vd.arrayLayerCount = 6;
1797 gpu->view = gpu->texture.CreateView(
reinterpret_cast<const wgpu::TextureViewDescriptor*
>(&vd));
1798 gpu->sampler = makeSampler(gpu->samplerState, gpu->mipLevels);
1800 auto* texture =
new Texture();
1801 texture->width = faceSize;
1802 texture->height = faceSize;
1803 texture->mipmapCount = int(gpu->mipLevels);
1804 texture->sampler = gpu->samplerState;
1805 texture->gpuHandle = gpu.get();
1806 ownedGpuTextures.push_back(std::move(gpu));
1807 ownedTextures.emplace_back(texture);
1813 if (!canvas || !canvas->hdr || !cubemap || !cubemap->
gpuHandle || face < 0 || face >= 6)
return false;
1818 wgpu::CommandEncoder encoder = device.CreateCommandEncoder();
1819 WGPUTexelCopyTextureInfo src{};
1820 src.texture = canvas->color.Get();
1822 src.aspect = WGPUTextureAspect_All;
1823 src.origin = {0, 0, 0};
1824 WGPUTexelCopyTextureInfo dst{};
1825 dst.texture =
target->texture.Get();
1827 dst.aspect = WGPUTextureAspect_All;
1828 dst.origin = {0, 0,
static_cast<uint32_t
>(face)};
1829 WGPUExtent3D extent{
static_cast<uint32_t
>(canvas->width),
static_cast<uint32_t
>(canvas->height), 1};
1830 encoder.CopyTextureToTexture(
reinterpret_cast<const wgpu::TexelCopyTextureInfo*
>(&src),
1831 reinterpret_cast<const wgpu::TexelCopyTextureInfo*
>(&dst),
1832 reinterpret_cast<const wgpu::Extent3D*
>(&extent));
1833 wgpu::CommandBuffer command = encoder.Finish();
1834 queue.Submit(1, &command);
1839 if (!sources || faceCount < 1 || faceCount > 6 || !cubemap || !cubemap->
gpuHandle)
return false;
1843 std::array<OffscreenCanvas*, 6> canvases{};
1844 for (
int face = 0; face < faceCount; ++face) {
1846 if (!canvas || !canvas->hdr ||
target->width != canvas->width ||
target->height != canvas->height)
return false;
1847 canvases[
static_cast<size_t>(face)] = canvas;
1850 wgpu::CommandEncoder encoder = device.CreateCommandEncoder();
1851 for (
int face = 0; face < faceCount; ++face) {
1852 const auto* canvas = canvases[
static_cast<size_t>(face)];
1853 WGPUTexelCopyTextureInfo src{};
1854 src.texture = canvas->color.Get();
1856 src.aspect = WGPUTextureAspect_All;
1857 src.origin = {0, 0, 0};
1858 WGPUTexelCopyTextureInfo dst{};
1859 dst.texture =
target->texture.Get();
1861 dst.aspect = WGPUTextureAspect_All;
1862 dst.origin = {0, 0,
static_cast<uint32_t
>(face)};
1863 WGPUExtent3D extent{
static_cast<uint32_t
>(canvas->width),
static_cast<uint32_t
>(canvas->height), 1};
1864 encoder.CopyTextureToTexture(
reinterpret_cast<const wgpu::TexelCopyTextureInfo*
>(&src),
1865 reinterpret_cast<const wgpu::TexelCopyTextureInfo*
>(&dst),
1866 reinterpret_cast<const wgpu::Extent3D*
>(&extent));
1868 wgpu::CommandBuffer command = encoder.Finish();
1869 queue.Submit(1, &command);
1874 if (!cubemap || !cubemap->
gpuHandle)
return false;
1877 sampleCount = std::clamp(sampleCount, 8, 512);
1878 wgpuInstanceProcessEvents(instance.Get());
1879 OffscreenTimestampReadback* timestampSlot =
nullptr;
1880 if (offscreenTimestampSupported && offscreenTimestampQuerySet && offscreenTimestampResolveBuffer) {
1881 for (
size_t candidate = 0; candidate < offscreenTimestampReadbacks.size(); ++candidate) {
1882 const size_t index = (offscreenTimestampReadbackCursor + candidate) % offscreenTimestampReadbacks.size();
1884 timestampSlot = &offscreenTimestampReadbacks[
index];
1885 offscreenTimestampReadbackCursor = (
index + 1) % offscreenTimestampReadbacks.size();
1891 if (!reflectionProbeFilterPipeline) {
1892 WGPUBindGroupLayoutEntry
entries[3]{};
1894 entries[0].visibility = WGPUShaderStage_Fragment;
1895 entries[0].texture.sampleType = WGPUTextureSampleType_Float;
1896 entries[0].texture.viewDimension = WGPUTextureViewDimension_Cube;
1898 entries[1].visibility = WGPUShaderStage_Fragment;
1899 entries[1].sampler.type = WGPUSamplerBindingType_Filtering;
1901 entries[2].visibility = WGPUShaderStage_Fragment;
1902 entries[2].buffer.type = WGPUBufferBindingType_Uniform;
1903 entries[2].buffer.hasDynamicOffset =
true;
1904 entries[2].buffer.minBindingSize = 16;
1905 WGPUBindGroupLayoutDescriptor bgl{};
1906 bgl.label =
sv(
"eve_reflection_probe_filter_bgl");
1909 reflectionProbeFilterSetLayout =
1910 device.CreateBindGroupLayout(
reinterpret_cast<const wgpu::BindGroupLayoutDescriptor*
>(&bgl));
1911 WGPUBindGroupLayout rawLayout = reflectionProbeFilterSetLayout.Get();
1912 WGPUPipelineLayoutDescriptor pld{};
1913 pld.label =
sv(
"eve_reflection_probe_filter_layout");
1914 pld.bindGroupLayoutCount = 1;
1915 pld.bindGroupLayouts = &rawLayout;
1916 reflectionProbeFilterPipelineLayout =
1917 device.CreatePipelineLayout(
reinterpret_cast<const wgpu::PipelineLayoutDescriptor*
>(&pld));
1919 wgpu::ShaderModule
module = makeWgslModule(device, shaders::kReflectionProbeFilterWgsl);
1920 WGPUColorTargetState colorTarget{};
1921 colorTarget.format = WGPUTextureFormat_RGBA16Float;
1922 colorTarget.writeMask = WGPUColorWriteMask_All;
1923 WGPUFragmentState fragment{};
1924 fragment.module =
module.Get();
1925 fragment.entryPoint =
sv(
"fs_main");
1926 fragment.targetCount = 1;
1927 fragment.targets = &colorTarget;
1928 WGPURenderPipelineDescriptor pipeline{};
1929 pipeline.label =
sv(
"eve_reflection_probe_filter");
1930 pipeline.layout = reflectionProbeFilterPipelineLayout.Get();
1931 pipeline.vertex.module =
module.Get();
1932 pipeline.vertex.entryPoint =
sv(
"vs_main");
1933 pipeline.fragment = &fragment;
1934 pipeline.primitive.topology = WGPUPrimitiveTopology_TriangleList;
1935 pipeline.primitive.frontFace = WGPUFrontFace_CCW;
1936 pipeline.primitive.cullMode = WGPUCullMode_None;
1937 pipeline.multisample.count = 1;
1938 pipeline.multisample.mask = 0xFFFFFFFFu;
1939 reflectionProbeFilterPipeline =
1940 device.CreateRenderPipeline(
reinterpret_cast<const wgpu::RenderPipelineDescriptor*
>(&pipeline));
1943 const uint32_t passCount = (
target->mipLevels - 1u) * 6u;
1944 WGPUBufferDescriptor bufferDesc{};
1945 bufferDesc.label =
sv(
"eve_reflection_probe_filter_params");
1946 bufferDesc.size = uint64_t(passCount) * 256u;
1947 bufferDesc.usage = WGPUBufferUsage_Uniform | WGPUBufferUsage_CopyDst;
1948 wgpu::Buffer paramsBuffer = device.CreateBuffer(
reinterpret_cast<const wgpu::BufferDescriptor*
>(&bufferDesc));
1949 WGPUTextureViewDescriptor sourceViewDesc{};
1950 sourceViewDesc.format = WGPUTextureFormat_RGBA16Float;
1951 sourceViewDesc.dimension = WGPUTextureViewDimension_Cube;
1952 sourceViewDesc.baseMipLevel = 0;
1953 sourceViewDesc.mipLevelCount = 1;
1954 sourceViewDesc.baseArrayLayer = 0;
1955 sourceViewDesc.arrayLayerCount = 6;
1956 wgpu::TextureView sourceView =
1957 target->texture.CreateView(
reinterpret_cast<const wgpu::TextureViewDescriptor*
>(&sourceViewDesc));
1958 WGPUBindGroupEntry groupEntries[3]{};
1959 groupEntries[0].binding = 0;
1960 groupEntries[0].textureView = sourceView.Get();
1961 groupEntries[1].binding = 1;
1962 groupEntries[1].sampler =
target->sampler.Get();
1963 groupEntries[2].binding = 2;
1964 groupEntries[2].buffer = paramsBuffer.Get();
1965 groupEntries[2].size = 16;
1966 WGPUBindGroupDescriptor groupDesc{};
1967 groupDesc.label =
sv(
"eve_reflection_probe_filter_group");
1968 groupDesc.layout = reflectionProbeFilterSetLayout.Get();
1969 groupDesc.entryCount = 3;
1970 groupDesc.entries = groupEntries;
1971 wgpu::BindGroup
group = device.CreateBindGroup(
reinterpret_cast<const wgpu::BindGroupDescriptor*
>(&groupDesc));
1973 wgpu::CommandEncoder encoder = device.CreateCommandEncoder();
1974 uint32_t passIndex = 0;
1975 for (uint32_t mip = 1; mip <
target->mipLevels; ++mip) {
1976 const bool diffuse = mip + 1u ==
target->mipLevels;
1977 const float roughness = diffuse ? 1.f : float(mip) / float(
target->mipLevels - 1u);
1978 for (uint32_t face = 0; face < 6; ++face, ++passIndex) {
1979 const float params[4] = {float(face),
roughness, diffuse ? 1.f : 0.f, float(sampleCount)};
1980 queue.WriteBuffer(paramsBuffer, uint64_t(passIndex) * 256u,
params,
sizeof(
params));
1981 WGPUTextureViewDescriptor viewDesc{};
1982 viewDesc.format = WGPUTextureFormat_RGBA16Float;
1983 viewDesc.dimension = WGPUTextureViewDimension_2D;
1984 viewDesc.baseMipLevel = mip;
1985 viewDesc.mipLevelCount = 1;
1986 viewDesc.baseArrayLayer = face;
1987 viewDesc.arrayLayerCount = 1;
1988 wgpu::TextureView faceView =
1989 target->texture.CreateView(
reinterpret_cast<const wgpu::TextureViewDescriptor*
>(&viewDesc));
1990 WGPURenderPassColorAttachment attachment{};
1991 attachment.view = faceView.Get();
1992 attachment.loadOp = WGPULoadOp_Clear;
1993 attachment.storeOp = WGPUStoreOp_Store;
1994 WGPURenderPassDescriptor passDesc{};
1995 passDesc.colorAttachmentCount = 1;
1996 passDesc.colorAttachments = &attachment;
1997 WGPUPassTimestampWrites timestampWrites{};
1998 if (timestampSlot) {
1999 timestampWrites.querySet = offscreenTimestampQuerySet.Get();
2000 timestampWrites.beginningOfPassWriteIndex = passIndex == 0 ? 0 : UINT32_MAX;
2001 timestampWrites.endOfPassWriteIndex = passIndex + 1u == passCount ? 1 : UINT32_MAX;
2002 passDesc.timestampWrites = ×tampWrites;
2004 wgpu::RenderPassEncoder pass =
2005 encoder.BeginRenderPass(
reinterpret_cast<const wgpu::RenderPassDescriptor*
>(&passDesc));
2006 pass.SetPipeline(reflectionProbeFilterPipeline);
2007 const uint32_t
offset = passIndex * 256u;
2013 if (timestampSlot) {
2014 encoder.ResolveQuerySet(offscreenTimestampQuerySet, 0, 2, offscreenTimestampResolveBuffer, 0);
2015 encoder.CopyBufferToBuffer(offscreenTimestampResolveBuffer, 0, timestampSlot->buffer, 0,
sizeof(uint64_t) * 2);
2017 wgpu::CommandBuffer command = encoder.Finish();
2018 queue.Submit(1, &command);
2019 if (timestampSlot) {
2020 timestampSlot->pending =
true;
2021 WGPUBufferMapCallbackInfo
callback{};
2022 callback.mode = WGPUCallbackMode_AllowProcessEvents;
2023 callback.callback = [](WGPUMapAsyncStatus
status, WGPUStringView,
void* userdata1,
void*) {
2024 auto* slot =
static_cast<OffscreenTimestampReadback*
>(userdata1);
2025 if (
status == WGPUMapAsyncStatus_Success && slot && slot->owner) {
2027 static_cast<const uint64_t*
>(slot->buffer.GetConstMappedRange(0,
sizeof(uint64_t) * 2));
2028 if (ticks && ticks[1] >= ticks[0]) {
2029 slot->owner->completedOffscreenTimestampMs.fetch_add(
float(ticks[1] - ticks[0]) * 1.0e-6f);
2031 slot->buffer.Unmap();
2033 if (slot) slot->pending =
false;
2035 callback.userdata1 = timestampSlot;
2036 wgpuBufferMapAsync(timestampSlot->buffer.Get(), WGPUMapMode_Read, 0,
sizeof(uint64_t) * 2,
callback);
2042 if (!texture)
return;
2043 auto* gpu = gpuForTexture(texture);
2046 gpu->sampler = makeSampler(
sampler, gpu->mipLevels);
2053 if (filename.empty())
throw Exception(
"newTextureFromFile: empty filename");
2056 auto it = texturesByPath.find(filename);
2057 if (it != texturesByPath.end() && it->second) {
2059 if (it->second->hasDeferredFilePixels())
return it->second;
2061 if (!waited.ok())
throw Exception(
"%s", waited.status().describe().c_str());
2063 if (!data || !
updateTexture(it->second, data->getWidth(), data->getHeight(),
2064 static_cast<const uint8_t*
>(data->getData())))
2065 throw Exception(
"newTextureFromFile: reload failed '%s'", filename.c_str());
2070 auto tex = std::make_unique<Texture>();
2071 tex->markDeferredFilePixels(
this);
2073 ownedTextures.push_back(std::move(tex));
2074 texturesByPath[filename] = raw;
2080 if (!texture || !data)
return false;
2082 return updateTexture(texture, data->getWidth(), data->getHeight(),
2083 static_cast<const uint8_t*
>(data->getData()));
2085 if (!fresh || !fresh->
gpuHandle)
return false;
2094 auto texIt = std::find_if(ownedTextures.begin(), ownedTextures.end(),
2095 [&](
const std::unique_ptr<Texture>&
t) { return t.get() == fresh; });
2096 if (texIt == ownedTextures.end())
return false;
2097 (void)texIt->release();
2098 ownedTextures.erase(texIt);
2104 auto it = texturesByPath.find(filename);
2105 if (it == texturesByPath.end())
return false;
2114 auto* imgMod = image::Image::create();
2115 data = imgMod->newImageDataFromFile(filename);
2116 if (data !=
nullptr) {
2118 if (!
pinned.ok())
return false;
2119 keepAlive = std::move(
pinned).takeValue();
2127 if (!data)
return false;
2130 return updateTexture(tex, data->getWidth(), data->getHeight(),
static_cast<const uint8_t*
>(data->getData()));
2134 if (!texture)
return false;
2137 auto texIt = std::find_if(ownedTextures.begin(), ownedTextures.end(),
2138 [&](
const std::unique_ptr<Texture>&
t) { return t.get() == texture; });
2139 if (texIt == ownedTextures.end())
return false;
2140 for (
auto it = texturesByPath.begin(); it != texturesByPath.end();) {
2141 if (it->second == texture)
2142 it = texturesByPath.erase(it);
2146 for (
auto it = sharedTexturesByContent.begin(); it != sharedTexturesByContent.end();) {
2147 if (it->second == texture)
2148 it = sharedTexturesByContent.erase(it);
2152 (void)texIt->release();
2153 ownedTextures.erase(texIt);
2158 texture->
gpuHandle == flatNormalTexture3D || texture->
gpuHandle == defaultEnvCubemap)
2162 auto gpuIt = std::find_if(ownedGpuTextures.begin(), ownedGpuTextures.end(),
2163 [&](
const std::unique_ptr<GpuTexture>&
g) { return g.get() == gpu; });
2164 if (gpuIt == ownedGpuTextures.end())
return false;
2166 auto texIt = std::find_if(ownedTextures.begin(), ownedTextures.end(),
2167 [&](
const std::unique_ptr<Texture>&
t) { return t.get() == texture; });
2168 if (texIt == ownedTextures.end())
return false;
2171 for (
auto it = texturesByPath.begin(); it != texturesByPath.end();) {
2172 if (it->second == texture)
2173 it = texturesByPath.erase(it);
2177 for (
auto it = sharedTexturesByContent.begin(); it != sharedTexturesByContent.end();) {
2178 if (it->second == texture)
2179 it = sharedTexturesByContent.erase(it);
2185 ownedGpuTextures.erase(gpuIt);
2187 (void)texIt->release();
2188 ownedTextures.erase(texIt);
2193 if (!texture || !rgba ||
width <= 0 ||
height <= 0)
return false;
2194 auto* gpu = gpuForTexture(texture);
2197 std::find_if(ownedGpuTextures.begin(), ownedGpuTextures.end(),
2198 [gpu](
const std::unique_ptr<GpuTexture>& candidate) { return candidate.get() == gpu; });
2199 if (
owned == ownedGpuTextures.end() || gpu->isCube)
return false;
2200 uploadTexturePixelsMips(gpu, rgba,
width,
height);
2205 std::span<const std::uint8_t> rgba, std::size_t bytesPerRow) {
2211 auto* gpu = gpuForTexture(texture);
2212 if (!texture || !gpu || gpu->isCube)
2214 "texture is not an owned 2D texture",
2215 "graphics.updateTextureRegions.texture"));
2218 "partial updates require a single-mip texture",
2219 "graphics.updateTextureRegions.mipmaps"));
2222 if (region.x < 0 || region.y < 0 || region.width <= 0 || region.height <= 0 ||
2223 region.x > texture->
width - region.width || region.y > texture->
height - region.height)
2225 "invalid texture region",
2226 "graphics.updateTextureRegions.region"));
2227 const std::size_t packedRow = std::size_t(region.width) * 4U;
2228 const std::size_t stride = region.bytesPerRow == 0 ? packedRow : region.bytesPerRow;
2229 const std::size_t requiredBytes = stride * std::size_t(region.height - 1) + packedRow;
2230 if (stride < packedRow || region.rgba.size() < requiredBytes)
2232 "source bytes do not cover the texture region",
2233 "graphics.updateTextureRegions.bytes"));
2237 const std::size_t packedRow = std::size_t(region.width) * 4U;
2238 const std::size_t stride = region.bytesPerRow == 0 ? packedRow : region.bytesPerRow;
2239 const std::size_t requiredBytes = stride * std::size_t(region.height - 1) + packedRow;
2240 WGPUTexelCopyTextureInfo dst{};
2241 dst.texture = gpu->texture.Get();
2243 dst.origin = {std::uint32_t(region.x), std::uint32_t(region.y), 0};
2244 dst.aspect = WGPUTextureAspect_All;
2245 WGPUTexelCopyBufferLayout
layout{};
2247 layout.bytesPerRow = std::uint32_t(stride);
2248 layout.rowsPerImage = std::uint32_t(region.height);
2249 WGPUExtent3D extent{std::uint32_t(region.width), std::uint32_t(region.height), 1};
2250 queue.WriteTexture(
reinterpret_cast<const wgpu::TexelCopyTextureInfo*
>(&dst), region.rgba.data(), requiredBytes,
2251 reinterpret_cast<const wgpu::TexelCopyBufferLayout*
>(&
layout),
2252 reinterpret_cast<const wgpu::Extent3D*
>(&extent));
2259GpuTexture* Graphics::gpuForTextureOrWhite(
Texture*
t)
const {
2260 GpuTexture*
g = gpuForTexture(
t);
2261 return g ?
g : whiteTexture;
2264wgpu::BindGroup Graphics::makeTex2DBindGroup(GpuTexture*
color, GpuTexture*
depth, GpuTexture* motion,
2265 GpuTexture* extra, GpuTexture* specular) {
2268 GpuTexture*
m = motion ? motion :
c;
2269 GpuTexture* e = extra ? extra :
c;
2270 GpuTexture*
s = specular ? specular :
c;
2271 WGPUBindGroupEntry
entries[11]{};
2275 entries[1].textureView =
d->view.Get();
2277 entries[2].sampler =
c->sampler.Get();
2279 entries[3].sampler =
d->sampler.Get();
2281 entries[4].buffer = currentUboArena().buffer.Get();
2284 entries[5].textureView =
m->view.Get();
2286 entries[6].sampler =
m->sampler.Get();
2288 entries[7].textureView = e->view.Get();
2290 entries[8].sampler = e->sampler.Get();
2292 entries[9].textureView =
s->view.Get();
2294 entries[10].sampler =
s->sampler.Get();
2295 WGPUBindGroupDescriptor
desc{};
2296 desc.label =
sv(
"eve_tex2d_group");
2297 desc.layout = tex2DSetLayout.Get();
2298 desc.entryCount = 11;
2300 return device.CreateBindGroup(
reinterpret_cast<const wgpu::BindGroupDescriptor*
>(&
desc));
2303wgpu::BindGroup Graphics::makeMeshBindGroup(GpuTexture*
albedo, GpuTexture*
normal, GpuTexture* env, GpuTexture*
height,
2304 GpuTexture*
depth, GpuTexture* sceneColor, uint32_t frameUboOffset,
2305 uint32_t shadowUboOffset, uint32_t pushUboOffset,
2306 const wgpu::Buffer& skinBuffer) {
2309 GpuTexture* e = env ? env : defaultEnvCubemap;
2310 auto localProbe = [&](
int index) -> GpuTexture* {
2313 if (gpu && gpu->isCube)
return gpu;
2315 return defaultEnvCubemap;
2317 GpuTexture* probe0 = localProbe(0);
2318 GpuTexture* probe1 = localProbe(1);
2320 GpuTexture*
d =
depth ?
depth : flatDepthTexture3D;
2321 GpuTexture*
c = sceneColor ? sceneColor : whiteTexture;
2322 GpuTexture* shadow = shadowDepthArray ? shadowDepthArray : defaultShadowTex;
2323 wgpu::TextureView aoView_ =
2324 aoReady ? aoView[(aoWriteIndex + 1) % 2] : (whiteTexture ? whiteTexture->
view : wgpu::TextureView());
2325 wgpu::TextureView decalAlbedoView = gpuForTextureOrWhite(decalFlatAlbedo)->
view;
2326 wgpu::TextureView decalNormalView = gpuForTextureOrWhite(decalFlatNormal)->
view;
2327 wgpu::TextureView decalParamsView = gpuForTextureOrWhite(decalFlatParams)->
view;
2328 if (decalReady && lastDecalSlot < decalSlots.size()) {
2329 decalAlbedoView = decalSlots[lastDecalSlot].albedoView;
2330 decalNormalView = decalSlots[lastDecalSlot].normalView;
2331 decalParamsView = decalSlots[lastDecalSlot].paramsView;
2334 MeshBindGroupKey
key{
reinterpret_cast<uintptr_t
>(currentUboArena().buffer.Get()),
2335 reinterpret_cast<uintptr_t
>(
a->view.Get()),
2336 reinterpret_cast<uintptr_t
>(
n->view.Get()),
2337 reinterpret_cast<uintptr_t
>(e->view.Get()),
2338 reinterpret_cast<uintptr_t
>(
h->view.Get()),
2339 reinterpret_cast<uintptr_t
>(
d->view.Get()),
2340 reinterpret_cast<uintptr_t
>(shadow->view.Get()),
2341 reinterpret_cast<uintptr_t
>(shadow->sampler.Get()),
2342 reinterpret_cast<uintptr_t
>(aoView_.Get()),
2343 reinterpret_cast<uintptr_t
>(decalAlbedoView.Get()),
2344 reinterpret_cast<uintptr_t
>(decalNormalView.Get()),
2345 reinterpret_cast<uintptr_t
>(decalParamsView.Get()),
2346 reinterpret_cast<uintptr_t
>(probe0->view.Get()),
2347 reinterpret_cast<uintptr_t
>(probe1->view.Get()),
2348 reinterpret_cast<uintptr_t
>(
c->view.Get()),
2349 reinterpret_cast<uintptr_t
>(skinBuffer.Get())};
2350 auto cached = meshBindGroupCache_.find(
key);
2351 if (cached != meshBindGroupCache_.end())
return cached->second;
2352 if (meshBindGroupCache_.size() >= kMaxMeshBindGroupCache) meshBindGroupCache_.clear();
2354 WGPUBindGroupEntry
entries[21]{};
2356 entries[0].buffer = currentUboArena().buffer.Get();
2357 entries[0].size =
sizeof(Mesh3DUBO);
2359 entries[1].textureView =
a->view.Get();
2361 entries[2].textureView =
n->view.Get();
2363 entries[3].textureView = e->view.Get();
2365 entries[4].buffer = currentUboArena().buffer.Get();
2366 entries[4].size =
sizeof(ShadowUBO);
2368 entries[5].textureView = shadow->view.Get();
2370 entries[6].textureView =
h->view.Get();
2376 entries[7].sampler =
a->sampler.Get();
2378 entries[8].sampler = shadow->sampler.Get();
2380 entries[9].textureView =
d->view.Get();
2382 entries[10].textureView = aoView_.Get();
2384 entries[11].sampler = mainSampler.Get();
2386 entries[12].textureView = decalAlbedoView.Get();
2388 entries[13].textureView = decalNormalView.Get();
2390 entries[14].textureView = decalParamsView.Get();
2392 entries[15].buffer = currentUboArena().buffer.Get();
2395 entries[16].textureView = probe0->view.Get();
2397 entries[17].textureView = probe1->view.Get();
2399 entries[18].textureView =
c->view.Get();
2401 entries[19].sampler =
c->sampler.Get();
2404 entries[20].buffer = skinBuffer.Get();
2405 entries[20].size = skinBuffer.GetSize();
2406 (void)frameUboOffset;
2407 (void)shadowUboOffset;
2408 (void)pushUboOffset;
2409 WGPUBindGroupDescriptor
desc{};
2410 desc.label =
sv(
"eve_mesh_group");
2411 desc.layout = mesh3dSetLayout.Get();
2412 desc.entryCount = 21;
2414 wgpu::BindGroup bg = device.CreateBindGroup(
reinterpret_cast<const wgpu::BindGroupDescriptor*
>(&
desc));
2415 meshBindGroupCache_.emplace(
key, bg);
2419wgpu::BindGroup Graphics::makeMesh3DClusteredBindGroup(GpuTexture*
albedo, GpuTexture*
normal, GpuTexture* env,
2420 GpuTexture*
height, GpuTexture*
depth, wgpu::TextureView aoView,
2421 uint32_t frameUboOffset, uint32_t shadowUboOffset) {
2424 GpuTexture* e = env ? env : defaultEnvCubemap;
2425 auto localProbe = [&](
int index) -> GpuTexture* {
2428 if (gpu && gpu->isCube)
return gpu;
2430 return defaultEnvCubemap;
2432 GpuTexture* probe0 = localProbe(0);
2433 GpuTexture* probe1 = localProbe(1);
2435 GpuTexture*
d =
depth ?
depth : flatDepthTexture3D;
2436 GpuTexture* shadow = shadowDepthArray ? shadowDepthArray : defaultShadowTex;
2437 ClusteredStorage& st = clusteredStorage[currentFrameSlot()];
2438 wgpu::TextureView decalAlbedoView = gpuForTextureOrWhite(decalFlatAlbedo)->
view;
2439 wgpu::TextureView decalNormalView = gpuForTextureOrWhite(decalFlatNormal)->
view;
2440 wgpu::TextureView decalParamsView = gpuForTextureOrWhite(decalFlatParams)->
view;
2441 if (decalReady && lastDecalSlot < decalSlots.size()) {
2442 decalAlbedoView = decalSlots[lastDecalSlot].albedoView;
2443 decalNormalView = decalSlots[lastDecalSlot].normalView;
2444 decalParamsView = decalSlots[lastDecalSlot].paramsView;
2447 WGPUBindGroupEntry
entries[20]{};
2449 entries[0].buffer = currentUboArena().buffer.Get();
2450 entries[0].size =
sizeof(Mesh3DClusteredUBO);
2452 entries[1].textureView =
a->view.Get();
2454 entries[2].textureView =
n->view.Get();
2456 entries[3].textureView = e->view.Get();
2458 entries[4].buffer = currentUboArena().buffer.Get();
2459 entries[4].size =
sizeof(ShadowUBO);
2461 entries[5].textureView = shadow->view.Get();
2463 entries[6].textureView =
h->view.Get();
2465 entries[7].sampler =
a->sampler.Get();
2467 entries[8].sampler = shadow->sampler.Get();
2469 entries[9].textureView =
d->view.Get();
2471 entries[10].buffer = st.lights.Get();
2472 entries[10].size = st.lightsCap;
2474 entries[11].buffer = st.table.Get();
2475 entries[11].size = st.tableCap;
2477 entries[12].buffer = st.indices.Get();
2478 entries[12].size = st.indicesCap;
2480 entries[13].textureView = aoView.Get();
2482 entries[14].sampler = mainSampler.Get();
2484 entries[15].textureView = decalAlbedoView.Get();
2486 entries[16].textureView = decalNormalView.Get();
2488 entries[17].textureView = decalParamsView.Get();
2490 entries[18].textureView = probe0->view.Get();
2492 entries[19].textureView = probe1->view.Get();
2494 (void)frameUboOffset;
2495 (void)shadowUboOffset;
2496 WGPUBindGroupDescriptor
desc{};
2497 desc.label =
sv(
"eve_mesh3d_clustered_group");
2498 desc.layout = mesh3dClusteredSetLayout.Get();
2499 desc.entryCount = 20;
2501 return device.CreateBindGroup(
reinterpret_cast<const wgpu::BindGroupDescriptor*
>(&
desc));
2518 throw Exception(
"newMeshFromArrays: invalid index data");
2525 auto&
v =
verts[
static_cast<size_t>(i)];
2526 v.pos = {posXYZ[i * 3 + 0], posXYZ[i * 3 + 1], posXYZ[i * 3 + 2]};
2528 v.normal = {nrmXYZ[i * 3 + 0], nrmXYZ[i * 3 + 1], nrmXYZ[i * 3 + 2]};
2530 v.normal = {0.f, 0.f, 1.f};
2533 v.uv = {uvST[i * 2 + 0], uvST[i * 2 + 1]};
2537 if (colorRGBA)
v.color = {colorRGBA[i*4],colorRGBA[i*4+1],colorRGBA[i*4+2],colorRGBA[i*4+3]};
2540 auto gpu = std::make_unique<GpuMesh>();
2544 gpu->cpuVertices =
verts;
2546 WGPUBufferDescriptor vbd{};
2547 vbd.label =
sv(
"eve_mesh_vb");
2549 vbd.usage = WGPUBufferUsage_CopyDst | WGPUBufferUsage_Vertex | WGPUBufferUsage_Storage;
2550 vbd.mappedAtCreation =
false;
2551 gpu->vertexBuffer = device.CreateBuffer(
reinterpret_cast<const wgpu::BufferDescriptor*
>(&vbd));
2552 queue.WriteBuffer(gpu->vertexBuffer, 0,
verts.data(), vbd.size);
2553 gpu->vertexCapacity = vbd.size;
2558 std::vector<uint16_t> idx16;
2561 WGPUBufferDescriptor ibd{};
2562 ibd.label =
sv(
"eve_mesh_ib");
2563 if (idx16.size() % 2 != 0) idx16.push_back(0);
2564 ibd.size = uint64_t(idx16.size()) *
sizeof(uint16_t);
2565 ibd.usage = WGPUBufferUsage_CopyDst | WGPUBufferUsage_Index | WGPUBufferUsage_Storage;
2566 ibd.mappedAtCreation =
false;
2567 gpu->indexBuffer = device.CreateBuffer(
reinterpret_cast<const wgpu::BufferDescriptor*
>(&ibd));
2568 queue.WriteBuffer(gpu->indexBuffer, 0, idx16.data(), ibd.size);
2569 gpu->indexFormat = wgpu::IndexFormat::Uint16;
2570 gpu->indexCapacity = ibd.size;
2572 WGPUBufferDescriptor ibd{};
2573 ibd.label =
sv(
"eve_mesh_ib");
2574 ibd.size = uint64_t(
indexCount) *
sizeof(uint32_t);
2575 ibd.usage = WGPUBufferUsage_CopyDst | WGPUBufferUsage_Index | WGPUBufferUsage_Storage;
2576 ibd.mappedAtCreation =
false;
2577 gpu->indexBuffer = device.CreateBuffer(
reinterpret_cast<const wgpu::BufferDescriptor*
>(&ibd));
2578 queue.WriteBuffer(gpu->indexBuffer, 0,
indices, ibd.size);
2579 gpu->indexFormat = wgpu::IndexFormat::Uint32;
2580 gpu->indexCapacity = ibd.size;
2587 mesh->gpuHandle = gpu.get();
2589 ownedGpuMeshes.push_back(std::move(gpu));
2590 ownedMeshes.push_back(std::unique_ptr<Mesh>(
mesh));
2595 if (!
mesh || !
mesh->gpuHandle)
return std::nullopt;
2596 const auto* gpu =
static_cast<const GpuMesh*
>(
mesh->gpuHandle);
2598 std::find_if(ownedGpuMeshes.begin(), ownedGpuMeshes.end(),
2599 [gpu](
const std::unique_ptr<GpuMesh>& candidate) { return candidate.get() == gpu; });
2600 if (
owned == ownedGpuMeshes.end())
return std::nullopt;
2602 gpu->indexFormat == wgpu::IndexFormat::Uint16 ? 2u : 4u};
2611 std::vector<float>
pos, nrm,
uv;
2612 std::vector<uint32_t>
idx;
2613 pos.reserve(
mesh.mNumVertices * 3);
2614 nrm.reserve(
mesh.mNumVertices * 3);
2615 uv.reserve(
mesh.mNumVertices * 2);
2616 idx.reserve(
mesh.mNumFaces * 3);
2618 for (
unsigned i = 0; i <
mesh.mNumVertices; ++i) {
2619 aiVector3D
p = worldTransform *
mesh.mVertices[i];
2623 if (
mesh.mNormals) {
2624 aiMatrix3x3 rot(worldTransform);
2625 aiVector3D
n = rot *
mesh.mNormals[i];
2635 if (
mesh.mTextureCoords[0]) {
2636 uv.push_back(
mesh.mTextureCoords[0][i].x);
2637 uv.push_back(
mesh.mTextureCoords[0][i].y);
2643 for (
unsigned f = 0;
f <
mesh.mNumFaces; ++
f) {
2644 for (
unsigned v = 0;
v <
mesh.mFaces[
f].mNumIndices; ++
v)
idx.push_back(
mesh.mFaces[
f].mIndices[
v]);
2647 if (
m)
m->captureImportedAttributes(
mesh);
2648 if (
m &&
mesh.mNumAnimMeshes > 0) {
2649 m->initMorphBase(
int(
mesh.mNumVertices),
pos.data(), nrm.data(),
uv.data());
2650 for (
unsigned a = 0;
a <
mesh.mNumAnimMeshes; ++
a) {
2651 const aiAnimMesh* am =
mesh.mAnimMeshes[
a];
2652 std::vector<float> absPos(am->mNumVertices * 3);
2653 for (
unsigned i = 0; i < am->mNumVertices; ++i) {
2654 absPos[i * 3 + 0] = am->mVertices[i].x;
2655 absPos[i * 3 + 1] = am->mVertices[i].y;
2656 absPos[i * 3 + 2] = am->mVertices[i].z;
2658 std::string
name = am->mName.C_Str();
2659 if (
name.empty())
name =
"morph" + std::to_string(
a);
2660 m->addMorphTargetAbsolute(
name, absPos.data());
2667 if (!
mesh || !
mesh->hasMorphData() || !
mesh->isMorphDirty())
return false;
2668 auto* gpu =
static_cast<GpuMesh*
>(
mesh->gpuHandle);
2669 if (!gpu || !gpu->vertexBuffer)
return false;
2670 std::vector<float>
pos, nrm;
2671 mesh->computeMorphedPositions(
pos, nrm);
2672 if (
pos.empty())
return false;
2673 mesh->computeBounds(
pos.data(),
mesh->getVertexCount());
2675 if (
verts.size() !=
static_cast<size_t>(
mesh->getVertexCount()))
return false;
2676 const auto& uvs =
mesh->baseUv();
2677 for (
int i = 0; i <
mesh->getVertexCount(); ++i) {
2678 auto&
v =
verts[
static_cast<size_t>(i)];
2679 v.pos = {
pos[i * 3 + 0],
pos[i * 3 + 1],
pos[i * 3 + 2]};
2680 if (nrm.size() >= size_t((i + 1) * 3)) {
2681 v.normal = {nrm[i * 3 + 0], nrm[i * 3 + 1], nrm[i * 3 + 2]};
2683 v.normal = {0.f, 0.f, 1.f};
2685 if (uvs.size() >= size_t((i + 1) * 2)) {
2686 v.uv = {uvs[i * 2 + 0], uvs[i * 2 + 1]};
2692 mesh->markMorphClean();
2703 auto* gpu =
static_cast<GpuMesh*
>(
mesh->gpuHandle);
2705 std::find_if(ownedGpuMeshes.begin(), ownedGpuMeshes.end(),
2706 [gpu](
const std::unique_ptr<GpuMesh>& candidate) { return candidate.get() == gpu; });
2707 if (
owned == ownedGpuMeshes.end())
return false;
2709 std::vector<float>
verts;
2712 verts.insert(
verts.end(), posXYZ +
size_t(i) * 3u, posXYZ +
size_t(i) * 3u + 3u);
2714 verts.insert(
verts.end(), nrmXYZ +
size_t(i) * 3u, nrmXYZ +
size_t(i) * 3u + 3u);
2718 verts.insert(
verts.end(), uvST +
size_t(i) * 2u, uvST +
size_t(i) * 2u + 2u);
2723 const uint64_t vertexBytes =
verts.size() *
sizeof(float);
2724 if (vertexBytes > gpu->vertexCapacity) {
2725 WGPUBufferDescriptor
desc{};
2726 desc.label =
sv(
"eve_mesh_dynamic_vb");
2727 desc.size = vertexBytes;
2728 desc.usage = WGPUBufferUsage_CopyDst | WGPUBufferUsage_Vertex | WGPUBufferUsage_Storage;
2729 gpu->vertexBuffer = device.CreateBuffer(
reinterpret_cast<const wgpu::BufferDescriptor*
>(&
desc));
2730 gpu->vertexCapacity = vertexBytes;
2732 queue.WriteBuffer(gpu->vertexBuffer, 0,
verts.data(), vertexBytes);
2736 const wgpu::IndexFormat format =
vertexCount <= 65535 ? wgpu::IndexFormat::Uint16 : wgpu::IndexFormat::Uint32;
2737 std::vector<uint16_t> idx16;
2738 const void* indexData =
indices;
2739 uint64_t indexBytes = uint64_t(
indexCount) *
sizeof(uint32_t);
2740 if (format == wgpu::IndexFormat::Uint16) {
2743 if (idx16.size() % 2 != 0) idx16.push_back(0);
2744 indexData = idx16.data();
2745 indexBytes = idx16.size() *
sizeof(uint16_t);
2747 if (format != gpu->indexFormat || indexBytes > gpu->indexCapacity) {
2748 WGPUBufferDescriptor
desc{};
2749 desc.label =
sv(
"eve_mesh_dynamic_ib");
2750 desc.size = indexBytes;
2751 desc.usage = WGPUBufferUsage_CopyDst | WGPUBufferUsage_Index | WGPUBufferUsage_Storage;
2752 gpu->indexBuffer = device.CreateBuffer(
reinterpret_cast<const wgpu::BufferDescriptor*
>(&
desc));
2753 gpu->indexCapacity = indexBytes;
2755 queue.WriteBuffer(gpu->indexBuffer, 0, indexData, indexBytes);
2756 gpu->indexFormat = format;
2761 mesh->gpuVertexCount = int(gpu->vertexCount);
2762 mesh->indexCount = int(gpu->indexCount);
2767 attrs[0].format = WGPUVertexFormat_Float32x3;
2768 attrs[0].offset = 0;
2769 attrs[0].shaderLocation = 0;
2770 attrs[1].format = WGPUVertexFormat_Float32x3;
2771 attrs[1].offset = 12;
2772 attrs[1].shaderLocation = 1;
2773 attrs[2].format = WGPUVertexFormat_Float32x2;
2774 attrs[2].offset = 24;
2775 attrs[2].shaderLocation = 2;
2776 attrs[3].format = WGPUVertexFormat_Uint16x4;
2777 attrs[3].offset = 32;
2778 attrs[3].shaderLocation = 3;
2779 attrs[4].format = WGPUVertexFormat_Float32x4;
2780 attrs[4].offset = 40;
2781 attrs[4].shaderLocation = 4;
2782 attrs[5].format = WGPUVertexFormat_Float32x4;
2783 attrs[5].offset = 56;
2784 attrs[5].shaderLocation = 5;
2788 if (!
mesh || !
mesh->gpuHandle || !joints4 || !weights4)
return false;
2789 auto* gpu =
static_cast<GpuMesh*
>(
mesh->gpuHandle);
2790 if (vertexCount <= 0 || gpu->cpuVertices.size() !=
static_cast<size_t>(
vertexCount))
return false;
2792 const size_t base =
static_cast<size_t>(i) * 4u;
2794 v.joints = glm::u16vec4(joints4[base], joints4[base + 1], joints4[base + 2], joints4[base + 3]);
2795 v.weights = glm::vec4(weights4[base], weights4[base + 1], weights4[base + 2], weights4[base + 3]);
2797 queue.WriteBuffer(gpu->vertexBuffer, 0, gpu->cpuVertices.data(), gpu->cpuVertices.size() *
sizeof(
MeshVertex));
2798 mesh->markGpuSkinned(
true);
2803 if (!
mesh || !
mesh->gpuHandle)
return false;
2805 auto* gpu =
static_cast<GpuMesh*
>(
mesh->gpuHandle);
2806 auto gpuIt = std::find_if(ownedGpuMeshes.begin(), ownedGpuMeshes.end(),
2807 [&](
const std::unique_ptr<GpuMesh>&
g) { return g.get() == gpu; });
2808 if (gpuIt == ownedGpuMeshes.end())
return false;
2810 auto meshIt = std::find_if(ownedMeshes.begin(), ownedMeshes.end(),
2811 [&](
const std::unique_ptr<Mesh>&
m) { return m.get() == mesh; });
2812 if (meshIt == ownedMeshes.end())
return false;
2814 mesh->gpuHandle =
nullptr;
2815 ownedGpuMeshes.erase(gpuIt);
2817 (void)meshIt->release();
2818 ownedMeshes.erase(meshIt);
2823 std::vector<float>
pos, nrm,
uv;
2824 std::vector<uint32_t>
idx;
2825 for (
int y = 0;
y <= stacks; ++
y) {
2826 for (
int x = 0;
x <= slices; ++
x) {
2827 float u = float(
x) / float(slices);
2828 float v = float(
y) / float(stacks);
2829 float theta =
u * 2.f * glm::pi<float>();
2830 float phi =
v * glm::pi<float>();
2831 float sx = std::sin(
phi) * std::cos(theta);
2832 float sy = std::cos(
phi);
2833 float sz = std::sin(
phi) * std::sin(theta);
2844 for (
int y = 0;
y < stacks; ++
y) {
2845 for (
int x = 0;
x < slices; ++
x) {
2846 int a =
y * (slices + 1) +
x;
2847 int b =
a + slices + 1;
2849 idx.push_back(
a + 1);
2852 idx.push_back(
a + 1);
2853 idx.push_back(
b + 1);
2860 std::vector<float>
pos, nrm,
uv;
2861 std::vector<uint32_t>
idx;
2862 for (
int y = 0;
y <= stacks; ++
y) {
2863 for (
int x = 0;
x <= slices; ++
x) {
2864 float u = float(
x) / float(slices);
2865 float v = float(
y) / float(stacks);
2866 float theta =
u * 2.f * glm::pi<float>();
2867 pos.push_back(std::cos(theta));
2868 pos.push_back(
v * 2.f - 1.f);
2869 pos.push_back(std::sin(theta));
2870 nrm.push_back(std::cos(theta));
2872 nrm.push_back(std::sin(theta));
2877 for (
int y = 0;
y < stacks; ++
y) {
2878 for (
int x = 0;
x < slices; ++
x) {
2879 int a =
y * (slices + 1) +
x;
2880 int b =
a + slices + 1;
2883 idx.push_back(
a + 1);
2885 idx.push_back(
b + 1);
2886 idx.push_back(
a + 1);
2890 int base = int(
pos.size() / 3);
2892 pos.push_back(-1.f);
2895 nrm.push_back(-1.f);
2899 for (
int x = 0;
x < slices; ++
x) {
2900 float theta0 = float(
x) / float(slices) * 2.f * glm::pi<float>();
2901 float theta1 = float(
x + 1) / float(slices) * 2.f * glm::pi<float>();
2902 int i0 = int(
pos.size() / 3);
2903 pos.push_back(std::cos(theta0));
2904 pos.push_back(-1.f);
2905 pos.push_back(std::sin(theta0));
2907 nrm.push_back(-1.f);
2909 uv.push_back(0.5f + 0.5f * std::cos(theta0));
2910 uv.push_back(0.5f + 0.5f * std::sin(theta0));
2911 int i1 = int(
pos.size() / 3);
2912 pos.push_back(std::cos(theta1));
2913 pos.push_back(-1.f);
2914 pos.push_back(std::sin(theta1));
2916 nrm.push_back(-1.f);
2918 uv.push_back(0.5f + 0.5f * std::cos(theta1));
2919 uv.push_back(0.5f + 0.5f * std::sin(theta1));
2920 idx.push_back(base);
2924 base = int(
pos.size() / 3);
2933 for (
int x = 0;
x < slices; ++
x) {
2934 float theta0 = float(
x) / float(slices) * 2.f * glm::pi<float>();
2935 float theta1 = float(
x + 1) / float(slices) * 2.f * glm::pi<float>();
2936 int i0 = int(
pos.size() / 3);
2937 pos.push_back(std::cos(theta0));
2939 pos.push_back(std::sin(theta0));
2943 uv.push_back(0.5f + 0.5f * std::cos(theta0));
2944 uv.push_back(0.5f + 0.5f * std::sin(theta0));
2945 int i1 = int(
pos.size() / 3);
2946 pos.push_back(std::cos(theta1));
2948 pos.push_back(std::sin(theta1));
2952 uv.push_back(0.5f + 0.5f * std::cos(theta1));
2953 uv.push_back(0.5f + 0.5f * std::sin(theta1));
2954 idx.push_back(base);
2966void Graphics::clear2DBatches() {
2967 solidBatches.clear();
2968 texturedBatches.clear();
2970 overlaySpans.clear();
2971 sceneColorComposited =
false;
2974void Graphics::noteSolidOverlay(uint32_t
idx) {
2975 if (
idx >= solidBatches.size())
return;
2976 const uint32_t
n = uint32_t(solidBatches[
idx].batch.vertices().size());
2977 if (!overlaySpans.empty() && overlaySpans.back().kind == OverlayKind::Solid && overlaySpans.back().index ==
idx) {
2978 overlaySpans.back().vertCount =
n - overlaySpans.back().vertBegin;
2981 const uint32_t
begin =
n >= 6u ?
n - 6u : 0
u;
2982 overlaySpans.push_back({OverlayKind::Solid,
idx,
begin,
n -
begin});
2985void Graphics::noteTexturedOverlay(Texture* tex, uint32_t
idx) {
2987 if (!overlaySpans.empty() && overlaySpans.back().kind == OverlayKind::Textured && overlaySpans.back().index ==
idx)
2989 overlaySpans.push_back({OverlayKind::Textured,
idx, 0, 0});
2992void Graphics::noteLitOverlay(uint32_t
idx) {
2993 if (!overlaySpans.empty() && overlaySpans.back().kind == OverlayKind::Lit && overlaySpans.back().index ==
idx)
2995 overlaySpans.push_back({OverlayKind::Lit,
idx, 0, 0});
3000 std::find_if(solidBatches.begin(), solidBatches.end(), [&](
const SolidBatch& sb) { return sb.blend == blend; });
3001 if (it == solidBatches.end()) {
3003 it = solidBatches.end() - 1;
3006 noteSolidOverlay(uint32_t(it - solidBatches.begin()));
3010 const int targetWidth = activeCanvas ? activeCanvas->
getWidth() :
getWidth();
3015 const glm::vec2 ndc = glm::vec2(vertex.clipPosition) / vertex.clipPosition.w;
3016 return glm::vec2((ndc.x + 1.f) * 0.5f *
static_cast<float>(targetWidth),
3017 (ndc.y + 1.f) * 0.5f *
static_cast<float>(targetHeight));
3020 auto it = std::find_if(solidBatches.begin(), solidBatches.end(),
3021 [&](
const SolidBatch& batch) { return batch.blend == resolvedBatch.blend; });
3022 if (it == solidBatches.end()) {
3024 it = solidBatches.end() - 1;
3026 for (std::size_t i = resolvedBatch.firstVertex; i < resolvedBatch.firstVertex + resolvedBatch.vertexCount;
3028 it->batch.addTriangle(logicalPoint(
triangles.vertices[i]), logicalPoint(
triangles.vertices[i + 1]),
3032 const auto batchIndex =
static_cast<std::uint32_t
>(it - solidBatches.begin());
3033 const std::uint32_t
count =
static_cast<std::uint32_t
>(resolvedBatch.vertexCount);
3034 const std::uint32_t
end =
static_cast<std::uint32_t
>(it->batch.vertices().size());
3035 overlaySpans.push_back({OverlayKind::Solid, batchIndex,
end -
count,
count});
3042 std::find_if(solidBatches.begin(), solidBatches.end(), [&](
const SolidBatch& sb) { return sb.blend == blend; });
3043 if (it == solidBatches.end()) {
3045 it = solidBatches.end() - 1;
3048 noteSolidOverlay(uint32_t(it - solidBatches.begin()));
3057 drawTexturedRectShaderUV(texture,
shader,
x,
y,
w,
h, 0.f, 0.f, 1.f, 1.f,
color);
3062 drawTexturedRectShaderUV(texture,
currentShader,
x,
y,
w,
h, u0, v0, u1, v1,
color);
3066 float v0,
float u1,
float v1,
const Color&
color,
bool rotatedUV,
3072 if (texturedBatches.empty() || texturedBatches.back().texture != texture ||
3073 texturedBatches.back().shader !=
shader || texturedBatches.back().depth !=
nullptr ||
3074 texturedBatches.back().blend !=
blend) {
3077 texturedBatches.back().batch.addTexturedRect(
x,
y,
w,
h,
color, u0, v0, u1, v1, rotatedUV);
3078 noteTexturedOverlay(texture, uint32_t(texturedBatches.size() - 1));
3082 float degrees,
float u0,
float v0,
float u1,
float v1,
3088 if (texturedBatches.empty() || texturedBatches.back().texture != texture ||
3089 texturedBatches.back().shader !=
shader || texturedBatches.back().depth !=
nullptr ||
3090 texturedBatches.back().blend !=
blend) {
3093 texturedBatches.back().batch.addTexturedRectRotated(
cx,
cy,
w,
h,
degrees,
color, u0, v0, u1, v1, rotatedUV);
3094 noteTexturedOverlay(texture, uint32_t(texturedBatches.size() - 1));
3103 if (texturedBatches.empty() || texturedBatches.back().texture !=
color || texturedBatches.back().shader !=
shader ||
3107 texturedBatches.back().batch.addTexturedRect(
x,
y,
w,
h,
tint, 0.f, 0.f, 1.f, 1.f,
false);
3108 noteTexturedOverlay(
color, uint32_t(texturedBatches.size() - 1));
3117 if (texturedBatches.empty() || texturedBatches.back().texture !=
color || texturedBatches.back().shader !=
shader ||
3118 texturedBatches.back().depth !=
depth || texturedBatches.back().motion != motion ||
3121 batch.motion = motion;
3122 texturedBatches.push_back(std::move(batch));
3124 texturedBatches.back().batch.addTexturedRect(
x,
y,
w,
h,
tint, 0.f, 0.f, 1.f, 1.f,
false);
3125 noteTexturedOverlay(
color, uint32_t(texturedBatches.size() - 1));
3134 if (texturedBatches.empty() || texturedBatches.back().texture !=
color || texturedBatches.back().shader !=
shader ||
3135 texturedBatches.back().depth !=
depth || texturedBatches.back().motion != motion ||
3136 texturedBatches.back().extra != extra || texturedBatches.back().blend !=
BlendMode::Opaque) {
3138 batch.motion = motion;
3139 batch.extra = extra;
3140 texturedBatches.push_back(std::move(batch));
3142 texturedBatches.back().batch.addTexturedRect(
x,
y,
w,
h,
tint, 0.f, 0.f, 1.f, 1.f,
false);
3143 noteTexturedOverlay(
color, uint32_t(texturedBatches.size() - 1));
3153 if (texturedBatches.empty() || texturedBatches.back().texture !=
color || texturedBatches.back().shader !=
shader ||
3154 texturedBatches.back().depth !=
depth || texturedBatches.back().motion != motion ||
3155 texturedBatches.back().extra != extra || texturedBatches.back().specular != specular ||
3158 batch.motion = motion;
3159 batch.extra = extra;
3160 batch.specular = specular;
3161 texturedBatches.push_back(std::move(batch));
3163 texturedBatches.back().batch.addTexturedRect(
x,
y,
w,
h,
tint, 0.f, 0.f, 1.f, 1.f,
false);
3164 noteTexturedOverlay(
color, uint32_t(texturedBatches.size() - 1));
3173 if (litBatches.empty() || litBatches.back().albedo !=
albedo || litBatches.back().normal !=
normal ||
3174 litBatches.back().blend !=
blend) {
3177 litBatches.back().batch.addTexturedRect(
x,
y,
w,
h,
color, u0, v0, u1, v1,
false);
3178 noteLitOverlay(uint32_t(litBatches.size() - 1));
3188 if (litBatches.empty() || litBatches.back().albedo !=
albedo || litBatches.back().normal !=
normal ||
3189 litBatches.back().blend !=
blend) {
3192 litBatches.back().batch.addTexturedRectRotated(
cx,
cy,
w,
h,
degrees,
color, u0, v0, u1, v1,
false);
3193 noteLitOverlay(uint32_t(litBatches.size() - 1));
3198 lighting2dFrame = ubo;
3201void Graphics::flush2D(wgpu::RenderPassEncoder pass,
int viewW,
int viewH, WGPUTextureFormat format) {
3203 auto spans = std::move(overlaySpans);
3204 const bool offscreen = uint32_t(format) != uint32_t(surfaceFormat);
3206 struct SolidUpload {
3211 std::vector<SolidUpload> solidUploads;
3212 solidUploads.reserve(solidBatches.size());
3213 for (
const auto& sb : solidBatches) {
3214 if (sb.batch.empty()) {
3215 solidUploads.push_back(SolidUpload{sb.blend, 0, 0});
3218 Batcher ndc = sb.batch;
3219 ndc.toNDC(viewW, viewH);
3220 auto verts = ndc.vertices();
3225 std::vector<float> data;
3226 data.reserve(
verts.size() * 6);
3227 for (
const auto&
v :
verts) {
3228 data.push_back(
v.pos.x);
3229 data.push_back(
v.pos.y);
3230 data.push_back(
v.color.r);
3231 data.push_back(
v.color.g);
3232 data.push_back(
v.color.b);
3233 data.push_back(
v.color.a);
3235 const uint64_t
bytes = data.size() *
sizeof(float);
3236 auto& arena = currentVertexArena();
3237 ensureVertexArena(arena, arena.used +
bytes);
3239 queue.WriteBuffer(arena.buffer,
offset, data.data(),
bytes);
3240 solidUploads.push_back(SolidUpload{sb.blend,
offset,
bytes});
3243 auto solidPipe = [&](
BlendMode mode) -> wgpu::RenderPipeline {
return get2DColorPipeline(mode, offscreen); };
3245 auto drawSolid = [&](uint32_t batchIndex, uint32_t
first, uint32_t
count) {
3246 if (batchIndex >= solidUploads.size())
return;
3247 const SolidUpload&
u = solidUploads[batchIndex];
3248 if (
u.bytes == 0 ||
count == 0)
return;
3249 pass.SetPipeline(solidPipe(
u.blend));
3250 pass.SetVertexBuffer(0, currentVertexArena().buffer,
u.offset,
u.bytes);
3254 if (!spans.empty()) {
3255 for (
const auto& sp : spans) {
3256 if (sp.kind == OverlayKind::Solid)
3257 drawSolid(sp.index, sp.vertBegin, sp.vertCount);
3258 else if (sp.kind == OverlayKind::Textured && sp.index < texturedBatches.size())
3259 drawTexturedBatch(pass, texturedBatches[sp.index], viewW, viewH, format, offscreen);
3260 else if (sp.kind == OverlayKind::Lit && sp.index < litBatches.size())
3261 drawLitBatch(pass, litBatches[sp.index], viewW, viewH, format);
3264 for (
size_t i = 0; i < solidUploads.size(); ++i) {
3265 if (solidUploads[i].
bytes > 0) drawSolid(uint32_t(i), 0, uint32_t(solidBatches[i].batch.vertices().size()));
3267 for (
auto&
tb : texturedBatches) {
3268 if (
tb.batch.empty())
continue;
3269 drawTexturedBatch(pass,
tb, viewW, viewH, format, offscreen);
3271 for (
auto& lb : litBatches) {
3272 if (lb.batch.empty())
continue;
3273 drawLitBatch(pass, lb, viewW, viewH, format);
3279void Graphics::drawTexturedBatch(wgpu::RenderPassEncoder pass, TexturedBatch&
tb,
int viewW,
int viewH,
3280 WGPUTextureFormat format,
bool offscreen) {
3281 Batcher ndc =
tb.batch;
3282 ndc.toNDC(viewW, viewH);
3283 auto verts = ndc.vertices();
3285 if (
verts.empty())
return;
3287 std::vector<float> data;
3288 data.reserve(
verts.size() * 8);
3289 for (
const auto&
v :
verts) {
3290 data.push_back(
v.pos.x);
3291 data.push_back(
v.pos.y);
3292 data.push_back(
v.color.r);
3293 data.push_back(
v.color.g);
3294 data.push_back(
v.color.b);
3295 data.push_back(
v.color.a);
3296 data.push_back(
v.uv.x);
3297 data.push_back(
v.uv.y);
3299 uint64_t
bytes = data.size() *
sizeof(float);
3300 auto& arena = currentVertexArena();
3301 ensureVertexArena(arena, arena.used +
bytes);
3302 uint64_t vtxOffset = arena.alloc(
bytes);
3303 queue.WriteBuffer(arena.buffer, vtxOffset, data.data(),
bytes);
3305 GpuTexture* gpu = gpuForTexture(
tb.texture);
3306 GpuTexture* depthGpu = gpuForTexture(
tb.depth);
3307 GpuTexture* motionGpu = gpuForTexture(
tb.motion);
3308 GpuTexture* extraGpu = gpuForTexture(
tb.extra);
3309 GpuTexture* specularGpu = gpuForTexture(
tb.specular);
3310 auto& uboArena = currentUboArena();
3311 ensureUboArena(uboArena, uboArena.used + 256);
3314 uint32_t pushOffset = 0;
3315 if (
tb.shader &&
tb.shader->pushConstantSize() > 0) {
3320 wgpu::BindGroup bg = makeTex2DBindGroup(gpu, depthGpu, motionGpu, extraGpu, specularGpu);
3321 uint32_t offsets[1] = {pushOffset};
3323 wgpu::RenderPipeline pipe;
3324 if (
tb.shader &&
tb.shader->gpuHandle) {
3325 auto* gs =
static_cast<GpuShader*
>(
tb.shader->gpuHandle);
3326 const bool hdr = format == WGPUTextureFormat_RGBA16Float;
3328 pipe = hdr ? gs->hdrOffscreenOpaquePipeline
3329 : (offscreen ? gs->offscreenOpaquePipeline : gs->swapchainOpaquePipeline);
3331 pipe = hdr ? gs->hdrOffscreenPipeline : (offscreen ? gs->offscreenPipeline : gs->swapchainPipeline);
3335 pipe = offscreen ? offscreenTexturedAdditivePipeline : texturedAdditivePipeline;
3338 pipe = offscreen ? offscreenTexturedPremultipliedPipeline : texturedPremultipliedPipeline;
3341 pipe = offscreen ? offscreenTexturedMultiplyPipeline : texturedMultiplyPipeline;
3344 pipe = format == WGPUTextureFormat_RGBA16Float
3345 ? hdrOffscreenTexturedOpaquePipeline
3346 : (offscreen ? offscreenTexturedOpaquePipeline : texturedOpaquePipeline);
3350 pipe = format == WGPUTextureFormat_RGBA16Float
3351 ? hdrOffscreenTexturedPipeline
3352 : (offscreen ? offscreenTexturedPipeline : texturedPipeline);
3357 pass.SetPipeline(pipe);
3358 pass.SetBindGroup(0, bg, 1, offsets);
3359 pass.SetVertexBuffer(0, arena.buffer, vtxOffset,
bytes);
3360 pass.Draw(
static_cast<uint32_t
>(
verts.size()), 1, 0, 0);
3363void Graphics::drawLitBatch(wgpu::RenderPassEncoder pass, LitBatch& lb,
int viewW,
int viewH,
3364 WGPUTextureFormat format) {
3365 Batcher ndc = lb.batch;
3366 ndc.toNDC(viewW, viewH);
3367 auto verts = ndc.vertices();
3369 if (
verts.empty())
return;
3371 std::vector<float> data;
3372 data.reserve(
verts.size() * 8);
3373 for (
const auto&
v :
verts) {
3374 data.push_back(
v.pos.x);
3375 data.push_back(
v.pos.y);
3376 data.push_back(
v.color.r);
3377 data.push_back(
v.color.g);
3378 data.push_back(
v.color.b);
3379 data.push_back(
v.color.a);
3380 data.push_back(
v.uv.x);
3381 data.push_back(
v.uv.y);
3383 uint64_t
bytes = data.size() *
sizeof(float);
3384 auto& arena = currentVertexArena();
3385 ensureVertexArena(arena, arena.used +
bytes);
3386 uint64_t vtxOffset = arena.alloc(
bytes);
3387 queue.WriteBuffer(arena.buffer, vtxOffset, data.data(),
bytes);
3389 GpuTexture* albedoGpu = gpuForTextureOrWhite(lb.albedo);
3392 GpuTexture* normalGpu = gpuForTexture(lb.normal);
3393 if (!normalGpu) normalGpu = flatNormalTexture ? flatNormalTexture : whiteTexture;
3395 auto& uboArena = currentUboArena();
3396 ensureUboArena(uboArena, uboArena.used + 512);
3397 uint32_t uboOffset = uboArena.alloc(
sizeof(Lighting2DUBO), 256);
3398 queue.WriteBuffer(uboArena.buffer, uboOffset, &lighting2dFrame,
sizeof(Lighting2DUBO));
3400 wgpu::BindGroup bg = makeTex2DBindGroup(albedoGpu, normalGpu);
3401 uint32_t offsets[1] = {uboOffset};
3402 wgpu::RenderPipeline pipe = get2DLitPipeline(lb.blend, format);
3404 pass.SetPipeline(pipe);
3405 pass.SetBindGroup(0, bg, 1, offsets);
3406 pass.SetVertexBuffer(0, arena.buffer, vtxOffset,
bytes);
3407 pass.Draw(
static_cast<uint32_t
>(
verts.size()), 1, 0, 0);
3415 if (!initialized || !device)
return;
3416 frame3DStarted =
true;
3417 frameHad3DThisFrame =
true;
3422 sceneColorPassOpen =
false;
3423 mesh3dDraws.clear();
3424 primitive3DDraws.clear();
3425 shadowPassDraws.clear();
3427 if (surfaceNeedsRecreate.load()) {
3428 surfaceNeedsRecreate.store(
false);
3429 markSwapchainDirty();
3431 rebuildSwapchainIfNeeded();
3436 if (!canvas)
throw Exception(
"begin3DFrameToCanvas: null canvas");
3437 if (!device)
throw Exception(
"begin3DFrameToCanvas: device not initialized");
3439 if (!offscreen)
throw Exception(
"begin3DFrameToCanvas: not an offscreen canvas");
3441 active3DCanvas = offscreen;
3446 if (!canvas || !device)
return;
3447 auto& uboArena = currentUboArena();
3449 ensureUboArena(uboArena, 4096);
3450 auto& vertexArena = currentVertexArena();
3451 vertexArena.reset();
3453 wgpuInstanceProcessEvents(instance.Get());
3454 OffscreenTimestampReadback* timestampSlot =
nullptr;
3455 if (offscreenTimestampSupported && offscreenTimestampQuerySet && offscreenTimestampResolveBuffer) {
3456 for (
size_t candidate = 0; candidate < offscreenTimestampReadbacks.size(); ++candidate) {
3457 const size_t index = (offscreenTimestampReadbackCursor + candidate) % offscreenTimestampReadbacks.size();
3459 timestampSlot = &offscreenTimestampReadbacks[
index];
3460 offscreenTimestampReadbackCursor = (
index + 1) % offscreenTimestampReadbacks.size();
3466 wgpu::CommandEncoder encoder = device.CreateCommandEncoder();
3467 WGPURenderPassColorAttachment
color{};
3468 color.view = canvas->colorView.Get();
3469 color.depthSlice = WGPU_DEPTH_SLICE_UNDEFINED;
3470 color.loadOp = WGPULoadOp_Clear;
3471 color.storeOp = WGPUStoreOp_Store;
3473 color.clearValue = {canvasClear.r, canvasClear.g, canvasClear.b, canvasClear.a};
3474 WGPURenderPassDepthStencilAttachment
depth{};
3475 depth.view = canvas->depthView.Get();
3476 depth.depthClearValue = 1.f;
3477 depth.depthLoadOp = WGPULoadOp_Clear;
3478 depth.depthStoreOp = WGPUStoreOp_Store;
3479 depth.stencilLoadOp = WGPULoadOp_Undefined;
3480 depth.stencilStoreOp = WGPUStoreOp_Undefined;
3485 WGPUPassTimestampWrites timestampWrites{};
3486 if (timestampSlot) {
3487 timestampWrites.querySet = offscreenTimestampQuerySet.Get();
3488 timestampWrites.beginningOfPassWriteIndex = 0;
3489 timestampWrites.endOfPassWriteIndex = 1;
3490 descriptor.timestampWrites = ×tampWrites;
3492 wgpu::RenderPassEncoder pass =
3493 encoder.BeginRenderPass(
reinterpret_cast<const wgpu::RenderPassDescriptor*
>(&
descriptor));
3494 flushMesh3D(pass, canvas->
isHDR() ? WGPUTextureFormat_RGBA16Float : WGPUTextureFormat_RGBA8Unorm,
true);
3495 flushPrimitive3D(pass, canvas->
isHDR() ? WGPUTextureFormat_RGBA16Float : WGPUTextureFormat_RGBA8Unorm, 1);
3497 if (timestampSlot) {
3498 encoder.ResolveQuerySet(offscreenTimestampQuerySet, 0, 2, offscreenTimestampResolveBuffer, 0);
3499 encoder.CopyBufferToBuffer(offscreenTimestampResolveBuffer, 0, timestampSlot->buffer, 0,
sizeof(uint64_t) * 2);
3501 wgpu::CommandBuffer command = encoder.Finish();
3502 queue.Submit(1, &command);
3503 if (timestampSlot) {
3504 timestampSlot->pending =
true;
3505 WGPUBufferMapCallbackInfo
callback{};
3506 callback.mode = WGPUCallbackMode_AllowProcessEvents;
3507 callback.callback = [](WGPUMapAsyncStatus
status, WGPUStringView,
void* userdata1,
void*) {
3508 auto* slot =
static_cast<OffscreenTimestampReadback*
>(userdata1);
3509 if (
status == WGPUMapAsyncStatus_Success && slot && slot->owner) {
3511 static_cast<const uint64_t*
>(slot->buffer.GetConstMappedRange(0,
sizeof(uint64_t) * 2));
3512 if (ticks && ticks[1] >= ticks[0]) {
3513 const float milliseconds = float(ticks[1] - ticks[0]) * 1.0e-6f;
3514 slot->owner->completedOffscreenTimestampMs.fetch_add(
milliseconds);
3516 slot->buffer.Unmap();
3518 if (slot) slot->pending =
false;
3520 callback.userdata1 = timestampSlot;
3521 wgpuBufferMapAsync(timestampSlot->buffer.Get(), WGPUMapMode_Read, 0,
sizeof(uint64_t) * 2,
callback);
3524 active3DCanvas =
nullptr;
3525 frame3DStarted =
false;
3526 frameHad3DThisFrame =
false;
3549 if (!
mesh || !
mesh->gpuHandle)
return;
3550 frameHad3DThisFrame =
true;
3554 d.texture = texture;
3555 d.normalTexture = mesh3dNormalTexture;
3556 d.heightTexture = mesh3dHeightTexture;
3557 d.virtualTexture = mesh3dVirtualTexture;
3558 d.virtualAtlas = mesh3dVirtualAtlas;
3562 d.surfaceMode = mesh3dSurfaceMode;
3563 d.surfaceBlend = mesh3dSurfaceBlend;
3564 d.depthWrite = mesh3dSurfaceDepthWrite;
3565 d.doubleSided = mesh3dSurfaceDoubleSided;
3566 d.shadowReceive = mesh3dShadowReceive;
3567 d.metallic = mesh3dMetallic;
3568 d.roughness = mesh3dRoughness;
3569 d.viewProj = mesh3dViewProj;
3570 d.view = mesh3dView;
3571 d.cameraPos = mesh3dCameraPos;
3572 d.lighting = mesh3dLighting;
3573 d.shadows = mesh3dShadows;
3574 d.environment = mesh3dEnvTexture;
3575 d.environmentIntensity = mesh3dEnvIntensity;
3576 d.skinInfluenceLimit = mesh3dSkinInfluenceLimit;
3577 d.alphaCutoff = mesh3dAlphaCutoff;
3578 d.alphaTechnique = mesh3dAlphaTechnique;
3579 d.lodFade = mesh3dLodFade;
3580 d.pbrSurface = mesh3dPbrSurface;
3581 mesh3dDraws.push_back(
d);
3589 float borderFraction) {
3590 mesh3dVirtualTexture =
3591 enabled ? glm::vec4(1.f,
float(pageCountX),
float(pageCountY), borderFraction) : glm::vec4(0.f);
3592 const float packedNormalMask = mesh3dVirtualAtlas.z;
3593 mesh3dVirtualAtlas =
enabled ? glm::vec4(
float(atlasSlotsX),
float(atlasSlotsY), packedNormalMask, 0.f)
3594 : glm::vec4(0.f, 0.f, packedNormalMask, 0.f);
3604 if (sceneColorSlots.size() < 2 || sceneColorSamples != 1)
3607 if (!mesh3dSceneColorCaptureIndex) mesh3dSceneColorCaptureIndex = mesh3dDraws.size();
3615 const std::string& alphaTechnique) {
3616 mesh3dSurfaceMode = mode;
3617 mesh3dSurfaceBlend =
blend;
3618 mesh3dSurfaceDepthWrite = depthWrite;
3620 mesh3dAlphaCutoff = std::clamp(
alphaCutoff, 0.f, 1.f);
3621 mesh3dAlphaTechnique = alphaTechnique;
3624 mesh3dTexBombScale = cellScale;
3626 mesh3dTexBombRot = rotAmount;
3629 mesh3dParallaxScale =
scale;
3630 mesh3dParallaxMin = minLayers;
3631 mesh3dParallaxMax = maxLayers;
3634 mesh3dLodFade = glm::vec4(std::clamp(
weight, 0.f, 1.f), reverse ? 1.f : 0.f,
3639 float coverage,
float detail) {
3640 mesh3dCloud = glm::vec4(std::clamp(
strength, 0.f, 1.f), std::max(worldCell, 1e-4f),
time, 0.f);
3641 mesh3dCloudWind = glm::vec4(std::cos(windAngle) * windSpeed, std::sin(windAngle) * windSpeed,
3642 std::clamp(coverage, 0.f, 1.f), std::clamp(
detail, 0.f, 1.f));
3645 mesh3dClustered = upload;
3646 mesh3dClusteredActive = upload.
active;
3647 if (upload.
active) uploadClusteredLighting(upload);
3653 if (!device)
return;
3654 ClusteredStorage& st = clusteredStorage[currentFrameSlot()];
3655 auto ensure = [&](wgpu::Buffer& buf, uint64_t& cap, uint64_t need) {
3656 if (need == 0) need = 4;
3657 if (buf && cap >= need)
return;
3658 WGPUBufferDescriptor bd{};
3659 bd.label =
sv(
"eve_clustered_ssbo");
3660 bd.size = std::max(need, cap ? cap * 2 : need);
3661 bd.usage = WGPUBufferUsage_CopyDst | WGPUBufferUsage_Storage;
3662 bd.mappedAtCreation =
false;
3663 buf = device.CreateBuffer(
reinterpret_cast<const wgpu::BufferDescriptor*
>(&bd));
3667 const uint64_t tableBytes = std::max<uint64_t>(1, upload.
clusterTable.size()) *
sizeof(ClusterTableEntry);
3668 const uint64_t indicesBytes = std::max<uint64_t>(1, upload.
lightIndices.size()) *
sizeof(uint32_t);
3669 ensure(st.lights, st.lightsCap, lightsBytes);
3670 ensure(st.table, st.tableCap, tableBytes);
3671 ensure(st.indices, st.indicesCap, indicesBytes);
3674 if (!upload.
lights.empty())
3675 queue.WriteBuffer(st.lights, 0, upload.
lights.data(), lightsBytes);
3677 queue.WriteBuffer(st.lights, 0, &zero,
sizeof(zero));
3687 mesh3dEnvTexture = cube;
3688 mesh3dEnvIntensity = intensity;
3692 mesh3dEnvProbeCenter =
center;
3693 mesh3dEnvProbeExtent = glm::max(extent, glm::vec3(0.f));
3700 mesh3dSkinInfluenceLimit =
static_cast<int>(
count);
3704 shadowPassCascade = cascadeIndex;
3705 shadowPassDraws.clear();
3709 if (!
mesh || !
mesh->gpuHandle)
return;
3714 d.skinInfluenceLimit = mesh3dSkinInfluenceLimit;
3715 shadowPassDraws.push_back(
d);
3721 if (!
mesh || !
mesh->gpuHandle)
return;
3728 d.skinInfluenceLimit = mesh3dSkinInfluenceLimit;
3730 lodDither ? 1.f : 0.f, 0.f);
3731 shadowPassDraws.push_back(
d);
3736 shadowPassCascade = -1;
3737 shadowPassDraws.clear();
3740 shadowCascadeDraws[shadowPassCascade] = shadowPassDraws;
3741 shadowPassDraws.clear();
3742 shadowPassCascade = -1;
3750 if (!device)
return;
3752 gbufferPassActive =
true;
3753 gbufferPassPending =
true;
3754 gbufferPassDraws.clear();
3758 Texture*
albedo,
float tintR,
float tintG,
float tintB,
float motionX,
float motionY,
3760 if (!
mesh || !
mesh->gpuHandle)
return;
3768 d.tint = glm::vec4(tintR, tintG, tintB, 1.f);
3769 d.motion = glm::vec2(motionX, motionY);
3772 d.skinInfluenceLimit = mesh3dSkinInfluenceLimit;
3773 gbufferPassDraws.push_back(
d);
3777 Texture*
albedo,
float tintR,
float tintG,
float tintB,
float motionX,
3779 if (!
mesh || !
mesh->gpuHandle)
return;
3787 d.tint = glm::vec4(tintR, tintG, tintB, 1.f);
3788 d.motion = glm::vec2(motionX, motionY);
3792 d.skinInfluenceLimit = mesh3dSkinInfluenceLimit;
3793 gbufferPassDraws.push_back(
d);
3797 gbufferPassActive =
false;
3800 if (!gbufferSlots.empty()) {
3801 GbufferSlot& slot = gbufferSlots[currentFrameSlot()];
3803 &slot.albedoTex, &slot.depthTex, &slot.pbrParamsTex,
3811 if (!decalFlatAlbedo) {
3812 const uint8_t transparent[4] = {0, 0, 0, 0};
3813 const uint8_t flatNormal[4] = {128, 128, 255, 255};
3814 const uint8_t neutralParams[4] = {128, 128, 0, 255};
3815 decalFlatAlbedo =
newTexture(1, 1, transparent);
3816 decalFlatNormal =
newTexture(1, 1, flatNormal);
3817 decalFlatParams =
newTexture(1, 1, neutralParams);
3819 decalPassActive =
true;
3820 decalPassPending =
false;
3821 decalPassDraws.clear();
3832 float normalStrength,
float roughnessStrength,
float metalStrength,
3833 float emissiveStrength,
int blendMode,
int projectionMode,
3834 float blendSharpness,
float parallaxScale,
float parallaxMinLayers,
3835 float parallaxMaxLayers,
float edgeFadeWidth) {
3836 if (!decalPassActive)
return;
3842 if (uvRect)
draw.uvRect = glm::vec4(uvRect[0], uvRect[1], uvRect[2], uvRect[3]);
3844 draw.extraParams = glm::vec4(emissiveStrength,
float(blendMode == 1),
3845 float(std::clamp(projectionMode, 0, 3)),
3846 blendSharpness > 0.f ? blendSharpness : 4.f);
3847 const float minLayers = std::clamp(parallaxMinLayers, 1.f, 64.f);
3848 draw.surfaceParams = glm::vec4(std::clamp(parallaxScale, 0.f, 1.f), minLayers,
3849 std::clamp(parallaxMaxLayers, minLayers, 64.f),
3850 std::clamp(edgeFadeWidth, 0.f, 0.49f));
3851 decalPassDraws.push_back(
draw);
3855 if (!decalPassActive)
return;
3856 decalPassActive =
false;
3857 decalPassPending =
true;
3869void Graphics::createSceneColorResources(
int width,
int height) {
3870 if (!device)
return;
3874 const uint32_t want = 1u;
3875 if (sceneColorWidth ==
width && sceneColorHeight ==
height && sceneColorSamples == want && !sceneColorSlots.empty())
3878 destroySceneColorResources();
3879 sceneColorWidth =
width;
3880 sceneColorHeight =
height;
3881 const uint32_t oldSamples = sceneColorSamples;
3882 const bool samplesChanged = oldSamples != want;
3883 sceneColorSamples = want;
3884 sceneColorSlots.reserve(kFramesInFlight);
3885 for (
int s = 0;
s < int(kFramesInFlight); ++
s) {
3886 sceneColorSlots.emplace_back();
3887 SceneColorSlot& slot = sceneColorSlots.back();
3888 slot.sampleCount = sceneColorSamples;
3890 WGPUTextureDescriptor cd{};
3891 cd.label =
sv(
"eve_scene_color");
3892 cd.dimension = WGPUTextureDimension_2D;
3893 cd.size = {
static_cast<uint32_t
>(
width),
static_cast<uint32_t
>(
height), 1};
3898 cd.format = sceneColorFormat;
3899 cd.mipLevelCount = 1;
3900 cd.usage = WGPUTextureUsage_TextureBinding | WGPUTextureUsage_RenderAttachment | WGPUTextureUsage_CopySrc;
3901 slot.color = device.CreateTexture(
reinterpret_cast<const wgpu::TextureDescriptor*
>(&cd));
3902 slot.colorView = slot.color.CreateView();
3904 if (sceneColorSamples > 1) {
3907 WGPUTextureDescriptor mcd{};
3908 mcd.label =
sv(
"eve_scene_color_msaa");
3909 mcd.dimension = WGPUTextureDimension_2D;
3910 mcd.size = {
static_cast<uint32_t
>(
width),
static_cast<uint32_t
>(
height), 1};
3911 mcd.sampleCount = sceneColorSamples;
3912 mcd.format = sceneColorFormat;
3913 mcd.mipLevelCount = 1;
3914 mcd.usage = WGPUTextureUsage_RenderAttachment;
3915 slot.msaaColor = device.CreateTexture(
reinterpret_cast<const wgpu::TextureDescriptor*
>(&mcd));
3916 slot.msaaView = slot.msaaColor.CreateView();
3919 WGPUTextureDescriptor dd{};
3920 dd.label =
sv(
"eve_scene_depth");
3921 dd.dimension = WGPUTextureDimension_2D;
3922 dd.size = {
static_cast<uint32_t
>(
width),
static_cast<uint32_t
>(
height), 1};
3923 dd.sampleCount = sceneColorSamples;
3924 dd.format = WGPUTextureFormat_Depth32Float;
3925 dd.mipLevelCount = 1;
3926 dd.usage = WGPUTextureUsage_RenderAttachment;
3927 slot.depth = device.CreateTexture(
reinterpret_cast<const wgpu::TextureDescriptor*
>(&dd));
3928 slot.depthView = slot.depth.CreateView();
3930 slot.colorGpu.texture = slot.color;
3931 slot.colorGpu.view = slot.colorView;
3932 slot.colorGpu.width =
width;
3933 slot.colorGpu.height =
height;
3934 slot.colorGpu.sampler = createLinearSampler(device);
3937 slot.colorTex.gpuHandle = &slot.colorGpu;
3938 slot.colorTex.width =
width;
3939 slot.colorTex.height =
height;
3940 slot.colorTex.mipmapCount = 1;
3942 sceneColorTexture = &sceneColorSlots[0].colorTex;
3946 if (samplesChanged) {
3947 mesh3dPipelines = {};
3948 mesh3dPipeline = {};
3949 mesh3dTransparentPipeline = {};
3950 mesh3dClusteredPipeline = {};
3951 voxelRectPipeline = {};
3953 destroyDeferredLightingResources();
3955 if (samplesChanged && gpuDrivenCullPipeline_) {
3959 gpuDrivenCullPipeline_ = {};
3960 gpuDrivenRenderPipeline_ = {};
3961 gpuDrivenCanvasPipeline_ = {};
3962 gpuDrivenComputePipelineLayout_ = {};
3963 gpuDrivenRenderPipelineLayout_ = {};
3964 gpuDrivenComputeSetLayout_ = {};
3965 gpuDrivenRenderSetLayout_ = {};
3966 gpuDrivenComputeBindGroup_ = {};
3967 gpuDrivenRenderBindGroup_ = {};
3971void Graphics::destroySceneColorResources() {
3972 sceneColorSlots.clear();
3973 sceneColorTexture =
nullptr;
3974 sceneColorHistoryValid =
false;
3979 if (!initialized || sceneColorWidth <= 0 || sceneColorHeight <= 0)
return;
3982 destroySceneColorResources();
3983 createSceneColorResources(sceneColorWidth, sceneColorHeight);
3986void Graphics::createShadowResources() {
3987 if (!device)
return;
3988 if (shadowDepthArray)
return;
3993 WGPUTextureDescriptor td{};
3994 td.label =
sv(
"eve_shadow_depth");
3995 td.dimension = WGPUTextureDimension_2D;
3996 td.size = {
static_cast<uint32_t
>(shadowMapSize),
static_cast<uint32_t
>(shadowMapSize),
3999 td.format = WGPUTextureFormat_Depth32Float;
4000 td.mipLevelCount = 1;
4001 td.usage = WGPUTextureUsage_TextureBinding | WGPUTextureUsage_RenderAttachment | WGPUTextureUsage_CopySrc |
4002 WGPUTextureUsage_CopyDst;
4003 gpu->texture = device.CreateTexture(
reinterpret_cast<const wgpu::TextureDescriptor*
>(&td));
4005 WGPUTextureViewDescriptor avd{};
4006 avd.format = WGPUTextureFormat_Depth32Float;
4007 avd.dimension = WGPUTextureViewDimension_2DArray;
4008 avd.baseMipLevel = 0;
4009 avd.mipLevelCount = 1;
4010 avd.baseArrayLayer = 0;
4011 avd.arrayLayerCount = 3;
4012 gpu->view = gpu->texture.CreateView(
reinterpret_cast<const wgpu::TextureViewDescriptor*
>(&avd));
4014 WGPUSamplerDescriptor sd{};
4015 sd.label =
sv(
"eve_shadow_sampler");
4016 sd.addressModeU = WGPUAddressMode_ClampToEdge;
4017 sd.addressModeV = WGPUAddressMode_ClampToEdge;
4018 sd.addressModeW = WGPUAddressMode_ClampToEdge;
4019 sd.magFilter = WGPUFilterMode_Linear;
4020 sd.minFilter = WGPUFilterMode_Linear;
4021 sd.mipmapFilter = WGPUMipmapFilterMode_Nearest;
4022 sd.compare = WGPUCompareFunction_LessEqual;
4023 sd.maxAnisotropy = 1.f;
4024 gpu->sampler = device.CreateSampler(
reinterpret_cast<const wgpu::SamplerDescriptor*
>(&sd));
4025 shadowDepthArray = gpu;
4028void Graphics::destroyShadowResources() { shadowDepthArray =
nullptr; }
4030void Graphics::createGbufferResources(
int width,
int height) {
4031 if (!device)
return;
4032 if (gbufferWidth ==
width && gbufferHeight ==
height && !gbufferSlots.empty())
return;
4033 destroyGbufferResources();
4034 gbufferWidth =
width;
4037 WGPUTextureDescriptor td{};
4038 td.label =
sv(
"eve_gbuffer_target");
4039 td.dimension = WGPUTextureDimension_2D;
4040 td.size = {
static_cast<uint32_t
>(
width),
static_cast<uint32_t
>(
height), 1};
4042 td.format = WGPUTextureFormat_RGBA8Unorm;
4043 td.mipLevelCount = 1;
4044 td.usage = WGPUTextureUsage_TextureBinding | WGPUTextureUsage_RenderAttachment | WGPUTextureUsage_CopySrc;
4046 WGPUTextureDescriptor dd{};
4047 dd.label =
sv(
"eve_gbuffer_depth");
4048 dd.dimension = WGPUTextureDimension_2D;
4049 dd.size = {
static_cast<uint32_t
>(
width),
static_cast<uint32_t
>(
height), 1};
4051 dd.format = WGPUTextureFormat_Depth32Float;
4052 dd.mipLevelCount = 1;
4054 dd.usage = WGPUTextureUsage_TextureBinding | WGPUTextureUsage_RenderAttachment;
4056 WGPUTextureDescriptor visIdDesc = td;
4057 visIdDesc.label =
sv(
"eve_visibility_id");
4058 visIdDesc.format = WGPUTextureFormat_RG32Uint;
4059 WGPUTextureDescriptor visBaryDesc = td;
4060 visBaryDesc.label =
sv(
"eve_visibility_bary");
4061 visBaryDesc.format = WGPUTextureFormat_RG16Float;
4063 gbufferSlots.reserve(kFramesInFlight);
4064 for (
int s = 0;
s < int(kFramesInFlight); ++
s) {
4065 gbufferSlots.emplace_back();
4066 GbufferSlot& slot = gbufferSlots.back();
4067 slot.normal = device.CreateTexture(
reinterpret_cast<const wgpu::TextureDescriptor*
>(&td));
4068 slot.normalView = slot.normal.CreateView();
4069 slot.depthColor = device.CreateTexture(
reinterpret_cast<const wgpu::TextureDescriptor*
>(&td));
4070 slot.depthColorView = slot.depthColor.CreateView();
4071 slot.albedo = device.CreateTexture(
reinterpret_cast<const wgpu::TextureDescriptor*
>(&td));
4072 slot.albedoView = slot.albedo.CreateView();
4073 slot.pbrParams = device.CreateTexture(
reinterpret_cast<const wgpu::TextureDescriptor*
>(&td));
4074 slot.pbrParamsView = slot.pbrParams.CreateView();
4075 slot.emissive = device.CreateTexture(
reinterpret_cast<const wgpu::TextureDescriptor*
>(&td));
4076 slot.emissiveView = slot.emissive.CreateView();
4077 slot.depth = device.CreateTexture(
reinterpret_cast<const wgpu::TextureDescriptor*
>(&dd));
4078 slot.depthView = slot.depth.CreateView();
4079 slot.visID = device.CreateTexture(
reinterpret_cast<const wgpu::TextureDescriptor*
>(&visIdDesc));
4080 slot.visIDView = slot.visID.CreateView();
4081 slot.visBary = device.CreateTexture(
reinterpret_cast<const wgpu::TextureDescriptor*
>(&visBaryDesc));
4082 slot.visBaryView = slot.visBary.CreateView();
4084 slot.normalGpu.texture = slot.normal;
4085 slot.normalGpu.view = slot.normalView;
4086 slot.normalGpu.sampler = createLinearSampler(device);
4087 slot.depthColorGpu.texture = slot.depthColor;
4088 slot.depthColorGpu.view = slot.depthColorView;
4089 slot.depthColorGpu.sampler = createLinearSampler(device);
4090 slot.albedoGpu.texture = slot.albedo;
4091 slot.albedoGpu.view = slot.albedoView;
4092 slot.albedoGpu.sampler = createLinearSampler(device);
4093 slot.pbrParamsGpu.texture = slot.pbrParams;
4094 slot.pbrParamsGpu.view = slot.pbrParamsView;
4095 slot.pbrParamsGpu.sampler = createLinearSampler(device);
4096 slot.emissiveGpu.texture = slot.emissive;
4097 slot.emissiveGpu.view = slot.emissiveView;
4098 slot.emissiveGpu.sampler = createLinearSampler(device);
4099 slot.depthGpu.texture = slot.depth;
4100 slot.depthGpu.view = slot.depthView;
4101 slot.depthGpu.sampler = createLinearSampler(device);
4103 slot.normalTex.gpuHandle = &slot.normalGpu;
4104 slot.normalTex.width =
width;
4105 slot.normalTex.height =
height;
4106 slot.depthColorTex.gpuHandle = &slot.depthColorGpu;
4107 slot.depthColorTex.width =
width;
4108 slot.depthColorTex.height =
height;
4109 slot.albedoTex.gpuHandle = &slot.albedoGpu;
4110 slot.albedoTex.width =
width;
4111 slot.albedoTex.height =
height;
4112 slot.pbrParamsTex.gpuHandle = &slot.pbrParamsGpu;
4113 slot.pbrParamsTex.width =
width;
4114 slot.pbrParamsTex.height =
height;
4115 slot.emissiveTex.gpuHandle = &slot.emissiveGpu;
4116 slot.emissiveTex.width =
width;
4117 slot.emissiveTex.height =
height;
4118 slot.depthTex.gpuHandle = &slot.depthGpu;
4119 slot.depthTex.width =
width;
4120 slot.depthTex.height =
height;
4124void Graphics::destroyGbufferResources() {
4125 gbufferSlots.clear();
4126 gbufferDepthValid_ =
false;
4129void Graphics::createDecalResources(
int width,
int height) {
4130 if (!device)
return;
4131 if (decalWidth ==
width && decalHeight ==
height && !decalSlots.empty())
return;
4132 destroyDecalResources();
4135 WGPUTextureDescriptor td{};
4136 td.label =
sv(
"eve_decal_target");
4137 td.dimension = WGPUTextureDimension_2D;
4138 td.size = {
static_cast<uint32_t
>(
width),
static_cast<uint32_t
>(
height), 1};
4140 td.format = WGPUTextureFormat_RGBA8Unorm;
4141 td.mipLevelCount = 1;
4142 td.usage = WGPUTextureUsage_TextureBinding | WGPUTextureUsage_RenderAttachment | WGPUTextureUsage_CopySrc;
4143 decalSlots.resize(kFramesInFlight);
4144 for (
auto& slot : decalSlots) {
4145 slot.albedo = device.CreateTexture(
reinterpret_cast<const wgpu::TextureDescriptor*
>(&td));
4146 slot.albedoView = slot.albedo.CreateView();
4147 slot.normal = device.CreateTexture(
reinterpret_cast<const wgpu::TextureDescriptor*
>(&td));
4148 slot.normalView = slot.normal.CreateView();
4149 slot.params = device.CreateTexture(
reinterpret_cast<const wgpu::TextureDescriptor*
>(&td));
4150 slot.paramsView = slot.params.CreateView();
4154void Graphics::destroyDecalResources() {
4165void Graphics::flushMesh3D(wgpu::RenderPassEncoder pass, WGPUTextureFormat format,
bool canvasTarget) {
4166 if (mesh3dDraws.empty())
return;
4168 if (mesh3dClusteredActive && !canvasTarget && !mesh3dClusteredPipeline) {
4169 for (
const auto&
d : mesh3dDraws) {
4170 const bool customShader =
d.shader &&
d.shader->gpuHandle;
4172 !
d.mesh->hasGpuSkinning()) {
4173 createMesh3DClusteredPipeline();
4179 auto& uboArena = currentUboArena();
4180 ensureUboArena(uboArena, uboArena.used + mesh3dDraws.size() * 10240);
4181 auto& vtxArena = currentVertexArena();
4184 for (
auto&
d : mesh3dDraws) {
4185 d.frameUboOffset = uboArena.alloc(
sizeof(Mesh3DUBO), 256);
4186 d.shadowUboOffset = uboArena.alloc(
sizeof(ShadowUBO), 256);
4187 d.clusteredUboOffset = 0;
4188 if (mesh3dClusteredActive && !canvasTarget &&
d.mesh && !
d.mesh->hasGpuSkinning())
4189 d.clusteredUboOffset = uboArena.alloc(
sizeof(Mesh3DClusteredUBO), 256);
4190 d.pushUboOffset = 0;
4191 if (
d.shader &&
d.shader->pushConstantSize() > 0)
4196 for (
auto&
d : mesh3dDraws) {
4197 if (mesh3dClusteredActive && !canvasTarget && mesh3dClusteredPipeline &&
d.clusteredUboOffset) {
4198 Mesh3DClusteredUBO cubo;
4199 cubo.mvp = mesh3dViewProj *
d.model;
4200 cubo.model =
d.model;
4201 cubo.view = mesh3dView;
4202 cubo.lightDir = glm::vec4(glm::vec3(mesh3dClustered.
primaryDir), mesh3dClustered.
primaryDir.w);
4203 cubo.lightColor = glm::vec4(glm::vec3(mesh3dClustered.
primaryColor), mesh3dEnvIntensity);
4205 cubo.cameraPos = glm::vec4(mesh3dCameraPos, mesh3dRoughness);
4206 cubo.ambient = glm::vec4(glm::vec3(mesh3dClustered.
ambient), mesh3dMetallic);
4207 cubo.gridInfo = mesh3dClustered.
gridInfo;
4208 cubo.clipInfo = mesh3dClustered.
clipInfo;
4209 cubo.texBomb = glm::vec4(mesh3dTexBombScale, mesh3dTexBombStrength, mesh3dTexBombRot, 0.f);
4210 cubo.parallax = glm::vec4(mesh3dParallaxScale, mesh3dParallaxMin, mesh3dParallaxMax, 0.f);
4211 cubo.virtualTexture =
d.virtualTexture;
4212 cubo.virtualAtlas =
d.virtualAtlas;
4213 float surfaceCode = float(
int(
d.surfaceMode));
4219 cubo.surface = glm::vec4(surfaceCode,
d.alphaCutoff,
aoStrength, 0.f);
4220 cubo.envProbeCenter = glm::vec4(mesh3dEnvProbeCenter, 1.f);
4221 cubo.envProbeExtent = glm::vec4(mesh3dEnvProbeExtent, 0.f);
4223 if (i >= mesh3dReflectionProbes.
count)
continue;
4224 const auto& probe = mesh3dReflectionProbes.
probes[i];
4225 GpuTexture* gpu = gpuForTexture(probe.cubemap);
4226 if (!gpu || !gpu->isCube)
continue;
4227 cubo.reflectionProbeCenter[i] = glm::vec4(probe.center, probe.intensity);
4228 cubo.reflectionProbeExtent[i] = glm::vec4(probe.extent, probe.blendDistance);
4230 queue.WriteBuffer(uboArena.buffer,
d.clusteredUboOffset, &cubo,
sizeof(cubo));
4234 ubo.mvp = mesh3dViewProj *
d.model;
4235 ubo.model =
d.model;
4236 ubo.lightDir = glm::vec4(glm::vec3(mesh3dLighting.
lights[0].
posRadius),
float(mesh3dLighting.
count));
4239 ubo.cameraPos = glm::vec4(mesh3dCameraPos, mesh3dRoughness);
4240 ubo.ambient = glm::vec4(glm::vec3(mesh3dLighting.
ambient), mesh3dMetallic);
4242 for (
size_t i = 0; i < mesh3dLighting.
diffuseProbeSh.size(); ++i)
4253 ubo.lodFade =
d.lodFade;
4254 float surfaceCode = float(
int(
d.surfaceMode));
4259 ubo.texBomb = glm::vec4(mesh3dTexBombScale, mesh3dTexBombStrength, mesh3dTexBombRot, 0.f);
4260 ubo.parallax = glm::vec4(mesh3dParallaxScale, mesh3dParallaxMin, mesh3dParallaxMax,
d.alphaCutoff);
4261 ubo.virtualTexture =
d.virtualTexture;
4262 ubo.virtualAtlas =
d.virtualAtlas;
4264 ubo.surface = glm::vec4(surfaceCode,
d.alphaCutoff,
aoStrength, 0.f);
4265 ubo.view = mesh3dView;
4266 ubo.clipInfo = glm::vec4(mesh3dNear, mesh3dFar, 0.f, 0.f);
4267 ubo.cloud = mesh3dCloud;
4268 ubo.cloudWind = mesh3dCloudWind;
4269 ubo.envProbeCenter = glm::vec4(mesh3dEnvProbeCenter, 1.f);
4270 ubo.envProbeExtent = glm::vec4(mesh3dEnvProbeExtent, 0.f);
4272 if (i >= mesh3dReflectionProbes.
count)
continue;
4273 const auto& probe = mesh3dReflectionProbes.
probes[i];
4274 GpuTexture* gpu = gpuForTexture(probe.cubemap);
4275 if (!gpu || !gpu->isCube)
continue;
4276 ubo.reflectionProbeCenter[i] = glm::vec4(probe.center, probe.intensity);
4277 ubo.reflectionProbeExtent[i] = glm::vec4(probe.extent, probe.blendDistance);
4279 ubo.lightColor.w = mesh3dEnvIntensity;
4280 if (
d.mesh &&
d.mesh->hasGpuSkinning()) ubo.skinInfo.x =
static_cast<float>(
d.mesh->getSkinPaletteCount());
4281 ubo.skinInfo.y =
static_cast<float>(
d.skinInfluenceLimit);
4282 d.skinBuffer = uploadSkinPalette(
d.mesh);
4286 if (
d.shader &&
d.shader->isXray() &&
d.shader->pushConstantSize() >= 9 *
sizeof(
float)) {
4287 const float* pc =
d.shader->pushConstantData();
4288 ubo.texBomb = glm::vec4(pc[0], pc[1], pc[2], pc[8]);
4289 ubo.parallax = glm::vec4(pc[3], pc[4], pc[5], pc[7]);
4290 ubo.clipInfo.z = pc[6];
4292 queue.WriteBuffer(uboArena.buffer,
d.frameUboOffset, &ubo,
sizeof(ubo));
4293 if (
d.shader &&
d.shader->pushConstantSize() > 0)
4294 queue.WriteBuffer(uboArena.buffer,
d.pushUboOffset,
d.shader->pushConstantData(),
4299 for (
auto&
d : mesh3dDraws) {
4300 ShadowUBO shadowUbo = mesh3dShadows.
ubo;
4301 if (!mesh3dShadows.
active || !
d.shadowReceive) shadowUbo.
bias.y = 0.f;
4302 queue.WriteBuffer(uboArena.buffer,
d.shadowUboOffset, &shadowUbo,
sizeof(shadowUbo));
4305 for (
auto&
d : mesh3dDraws) {
4306 auto* gpuMesh =
static_cast<GpuMesh*
>(
d.mesh->gpuHandle);
4307 if (!gpuMesh || !gpuMesh->vertexBuffer)
continue;
4311 const bool depthWrite = !transparent ||
d.depthWrite;
4314 const bool customShader =
d.shader &&
d.shader->gpuHandle;
4315 if (
d.pbrSurface && !customShader &&
4316 drawPbrMesh(pass, format, canvasTarget,
d, *gpuMesh))
4318 wgpu::RenderPipeline pipe;
4319 if (
d.shader &&
d.shader->gpuHandle) {
4320 auto* gs =
static_cast<GpuShader*
>(
d.shader->gpuHandle);
4321 if (gs->isMesh3D && gs->mesh3dPipeline) {
4322 if (
d.shader->isXray() && gs->mesh3dXrayPipeline)
4323 pipe = gs->mesh3dXrayPipeline;
4325 pipe = gs->mesh3dPipeline;
4328 if (!pipe) pipe = getMesh3DPipeline(
blend, depthWrite,
d.doubleSided, canvasTarget);
4329 const bool useClustered = !canvasTarget && !customShader && !
d.doubleSided && mesh3dClusteredActive &&
4331 d.clusteredUboOffset && !
d.mesh->hasGpuSkinning();
4332 if (useClustered) pipe = mesh3dClusteredPipeline;
4333 if (!pipe)
continue;
4334 pass.SetPipeline(pipe);
4336 GpuTexture*
albedo = gpuForTexture(
d.texture);
4337 GpuTexture*
normal = gpuForTexture(
d.normalTexture);
4338 GpuTexture* env = gpuForTexture(mesh3dEnvTexture);
4339 GpuTexture*
height = gpuForTexture(
d.heightTexture);
4340 GpuTexture*
depth = mesh3dSceneDepthTexture ? gpuForTexture(mesh3dSceneDepthTexture) : flatDepthTexture3D;
4341 GpuTexture* sceneColor = mesh3dSceneColorTexture ? gpuForTexture(mesh3dSceneColorTexture)
4342 : sceneColorHistoryValid && lastPresentSlot < sceneColorSlots.
size()
4343 ? &sceneColorSlots[lastPresentSlot].colorGpu
4346 uint32_t offsets[3];
4348 wgpu::TextureView aoView_ =
4349 aoReady ? aoView[(aoWriteIndex + 1) % 2] : (whiteTexture ? whiteTexture->
view : wgpu::TextureView());
4352 offsets[0] =
d.clusteredUboOffset;
4353 offsets[1] =
d.shadowUboOffset;
4354 pass.SetBindGroup(0, bg, 2, offsets);
4357 d.pushUboOffset,
d.skinBuffer);
4358 offsets[0] =
d.frameUboOffset;
4359 offsets[1] =
d.shadowUboOffset;
4360 offsets[2] =
d.pushUboOffset;
4361 pass.SetBindGroup(0, bg, 3, offsets);
4364 if (gpuMesh->indexBuffer) {
4365 pass.SetVertexBuffer(0, gpuMesh->vertexBuffer, 0, uint64_t(gpuMesh->vertexCount) * gpuMesh->vertexStride);
4366 const uint64_t indexBytes = gpuMesh->indexFormat == wgpu::IndexFormat::Uint16 ? 2u : 4u;
4367 pass.SetIndexBuffer(gpuMesh->indexBuffer, gpuMesh->indexFormat, 0,
4368 uint64_t(gpuMesh->indexCount) * indexBytes);
4369 pass.DrawIndexed(gpuMesh->indexCount, 1, 0, 0, 0);
4371 pass.SetVertexBuffer(0, gpuMesh->vertexBuffer, 0, uint64_t(gpuMesh->vertexCount) * gpuMesh->vertexStride);
4372 pass.Draw(gpuMesh->vertexCount, 1, 0, 0);
4375 mesh3dDraws.clear();
4378void Graphics::flushShadowPass(wgpu::RenderPassEncoder pass,
int cascade) {
4379 auto& uboArena = currentUboArena();
4381 if (!mesh3dShadowPipeline) createShadowPipelines();
4382 ensureUboArena(uboArena, uboArena.used + shadowCascadeDraws[cascade].size() * 256);
4383 for (
auto&
d : shadowCascadeDraws[cascade]) {
4384 auto* gpuMesh =
static_cast<GpuMesh*
>(
d.mesh->gpuHandle);
4385 if (!gpuMesh || !gpuMesh->vertexBuffer)
continue;
4387 pass.SetPipeline(
d.alphaTest
4388 ? (
d.doubleSided ? mesh3dShadowAlphaPipeline : mesh3dShadowAlphaSingleSidedPipeline)
4389 : (
d.
doubleSided ? mesh3dShadowPipeline : mesh3dShadowSingleSidedPipeline));
4393 ubo.clip =
d.lodFade;
4394 if (
d.mesh->hasGpuSkinning()) ubo.skinInfo.x =
static_cast<float>(
d.mesh->getSkinPaletteCount());
4395 ubo.skinInfo.y =
static_cast<float>(
d.skinInfluenceLimit);
4396 const auto skinBuffer = uploadSkinPalette(
d.mesh);
4397 uint32_t
offset = uboArena.alloc(
sizeof(SkinPassUBO), 256);
4398 queue.WriteBuffer(uboArena.buffer,
offset, &ubo,
sizeof(ubo));
4400 GpuTexture*
albedo = gpuForTextureOrWhite(
d.albedo);
4401 WGPUBindGroupEntry
entries[4]{};
4403 entries[0].buffer = uboArena.buffer.Get();
4404 entries[0].size =
sizeof(SkinPassUBO);
4410 entries[3].buffer = skinBuffer.Get();
4411 entries[3].size = skinBuffer.GetSize();
4412 WGPUBindGroupDescriptor bgd{};
4413 bgd.layout = shadowSetLayout.Get();
4416 wgpu::BindGroup bg = device.CreateBindGroup(
reinterpret_cast<const wgpu::BindGroupDescriptor*
>(&bgd));
4417 uint32_t offsets[1] = {
offset};
4418 pass.SetBindGroup(0, bg, 1, offsets);
4420 if (gpuMesh->indexBuffer) {
4421 pass.SetVertexBuffer(0, gpuMesh->vertexBuffer, 0, uint64_t(gpuMesh->vertexCount) * gpuMesh->vertexStride);
4422 const uint64_t indexBytes = gpuMesh->indexFormat == wgpu::IndexFormat::Uint16 ? 2u : 4u;
4423 pass.SetIndexBuffer(gpuMesh->indexBuffer, gpuMesh->indexFormat, 0,
4424 uint64_t(gpuMesh->indexCount) * indexBytes);
4425 pass.DrawIndexed(gpuMesh->indexCount, 1, 0, 0, 0);
4427 pass.SetVertexBuffer(0, gpuMesh->vertexBuffer, 0, uint64_t(gpuMesh->vertexCount) * gpuMesh->vertexStride);
4428 pass.Draw(gpuMesh->vertexCount, 1, 0, 0);
4431 shadowCascadeDraws[cascade].clear();
4434void Graphics::flushGbufferPass(wgpu::RenderPassEncoder pass) {
4435 if (gbufferPassDraws.empty() || gbufferSlots.empty())
return;
4436 if (!mesh3dGbufferPipeline) createGbufferPipelines();
4437 auto& uboArena = currentUboArena();
4438 ensureUboArena(uboArena, uboArena.used + gbufferPassDraws.size() * 256);
4439 for (
auto&
d : gbufferPassDraws) {
4440 auto* gpuMesh =
static_cast<GpuMesh*
>(
d.mesh->gpuHandle);
4441 if (!gpuMesh || !gpuMesh->vertexBuffer)
continue;
4443 pass.SetPipeline(
d.alphaTest ? mesh3dGbufferAlphaPipeline : mesh3dGbufferPipeline);
4447 ubo.model =
d.model;
4448 auto u6 = [](
float value) {
return uint32_t(std::lround(std::clamp(
value, 0.f, 1.f) * 63.f)); };
4450 const uint32_t rough7 = uint32_t(std::lround(std::clamp(
d.roughness, 0.f, 1.f) * 127.f));
4451 const uint32_t metal7 = uint32_t(std::lround(std::clamp(
d.metallic, 0.f, 1.f) * 127.f));
4452 const uint32_t packedTint =
4453 u6(
d.tint.r) | (u6(
d.tint.g) << 6) | (u6(
d.tint.b) << 12) | (rough7 << 18) | (metal7 << 25);
4454 auto motion12 = [](
float value) {
4455 return uint32_t(std::lround(std::clamp(
value, -1.f, 1.f) * 2047.f)) + 2047u;
4457 const uint32_t packedMotion = motion12(
d.motion.x) | (motion12(
d.motion.y) << 12);
4458 ubo.clip = glm::vec4(
d.nearZ,
d.farZ, std::bit_cast<float>(packedTint),
float(packedMotion));
4459 if (
d.mesh->hasGpuSkinning()) ubo.skinInfo.x =
static_cast<float>(
d.mesh->getSkinPaletteCount());
4460 ubo.skinInfo.y =
static_cast<float>(
d.skinInfluenceLimit);
4461 const auto skinBuffer = uploadSkinPalette(
d.mesh);
4463 uint32_t
offset = uboArena.alloc(
sizeof(SkinPassUBO), 256);
4464 queue.WriteBuffer(uboArena.buffer,
offset, &ubo,
sizeof(ubo));
4466 GpuTexture*
albedo = gpuForTextureOrWhite(
d.albedo);
4467 WGPUBindGroupEntry
entries[4]{};
4469 entries[0].buffer = uboArena.buffer.Get();
4470 entries[0].size =
sizeof(SkinPassUBO);
4476 entries[3].buffer = skinBuffer.Get();
4477 entries[3].size = skinBuffer.GetSize();
4478 WGPUBindGroupDescriptor bgd{};
4479 bgd.layout = gbufferSetLayout.Get();
4482 wgpu::BindGroup bg = device.CreateBindGroup(
reinterpret_cast<const wgpu::BindGroupDescriptor*
>(&bgd));
4483 uint32_t offsets[1] = {
offset};
4484 pass.SetBindGroup(0, bg, 1, offsets);
4486 if (gpuMesh->indexBuffer) {
4487 pass.SetVertexBuffer(0, gpuMesh->vertexBuffer, 0, uint64_t(gpuMesh->vertexCount) * gpuMesh->vertexStride);
4488 const uint64_t indexBytes = gpuMesh->indexFormat == wgpu::IndexFormat::Uint16 ? 2u : 4u;
4489 pass.SetIndexBuffer(gpuMesh->indexBuffer, gpuMesh->indexFormat, 0,
4490 uint64_t(gpuMesh->indexCount) * indexBytes);
4491 pass.DrawIndexed(gpuMesh->indexCount, 1, 0, 0, 0);
4493 pass.SetVertexBuffer(0, gpuMesh->vertexBuffer, 0, uint64_t(gpuMesh->vertexCount) * gpuMesh->vertexStride);
4494 pass.Draw(gpuMesh->vertexCount, 1, 0, 0);
4497 gbufferPassDraws.clear();
4498 gbufferPassPending =
false;
4501void Graphics::flushDecalPass(wgpu::RenderPassEncoder pass) {
4502 if (decalPassDraws.empty() || gbufferSlots.empty()) {
4503 decalPassPending =
false;
4506 if (!decalPipeline) createDecalPipeline();
4507 auto& uboArena = currentUboArena();
4508 ensureUboArena(uboArena, uboArena.used + decalPassDraws.size() * 512);
4509 GbufferSlot& gbuffer = gbufferSlots[lastGbufferSlot];
4510 pass.SetPipeline(decalPipeline);
4511 struct DecalUniforms {
4518 glm::vec4 fadeParams;
4519 glm::vec4 extraParams;
4520 glm::vec4 surfaceParams;
4523 static_assert(
sizeof(DecalUniforms) == 256);
4524 for (
const auto&
draw : decalPassDraws) {
4525 DecalUniforms uniforms{};
4526 uniforms.invViewProj = glm::inverse(decalViewProj);
4527 uniforms.invModel = glm::inverse(
draw.model);
4528 uniforms.modelR0 =
draw.model[0];
4529 uniforms.modelR1 =
draw.model[1];
4530 uniforms.modelR2 =
draw.model[2];
4531 uniforms.uvRect =
draw.uvRect;
4532 uniforms.fadeParams =
draw.fadeParams;
4533 uniforms.extraParams =
draw.extraParams;
4534 uniforms.surfaceParams =
draw.surfaceParams;
4535 uniforms.texel = glm::vec4(1.f /
float(decalWidth), 1.f /
float(decalHeight), 0.f, 0.f);
4536 uint32_t
offset = uboArena.alloc(256, 256);
4537 queue.WriteBuffer(uboArena.buffer,
offset, &uniforms,
sizeof(uniforms));
4538 GpuTexture*
albedo = gpuForTextureOrWhite(
draw.albedo);
4539 GpuTexture*
normal = gpuForTextureOrWhite(
draw.normal);
4540 GpuTexture*
params = gpuForTextureOrWhite(
draw.params);
4541 WGPUBindGroupEntry
entries[7]{};
4543 entries[0].buffer = uboArena.buffer.Get();
4544 entries[0].size =
sizeof(uniforms);
4552 entries[4].textureView = gbuffer.depthView.Get();
4554 entries[5].textureView = gbuffer.normalView.Get();
4556 entries[6].sampler = mainSampler.Get();
4561 wgpu::BindGroup
group = device.CreateBindGroup(
reinterpret_cast<const wgpu::BindGroupDescriptor*
>(&
descriptor));
4562 uint32_t offsets[1] = {
offset};
4563 pass.SetBindGroup(0,
group, 1, offsets);
4564 pass.Draw(3, 1, 0, 0);
4566 decalPassDraws.clear();
4567 decalPassPending =
false;
4569 clearMeshBindGroupCache();
4572bool Graphics::flushGBufferPassInto(wgpu::CommandEncoder& encoder,
bool runAo) {
4573 if (!gbufferPassPending || gbufferSlots.empty())
return false;
4574 lastGbufferSlot = currentFrameSlot();
4575 GbufferSlot& slot = gbufferSlots[lastGbufferSlot];
4576 WGPURenderPassColorAttachment colorAtts[5]{};
4577 for (
int i = 0; i < 5; ++i) {
4578 colorAtts[i].depthSlice = WGPU_DEPTH_SLICE_UNDEFINED;
4579 colorAtts[i].loadOp = WGPULoadOp_Clear;
4580 colorAtts[i].storeOp = WGPUStoreOp_Store;
4581 colorAtts[i].clearValue = {0.f, 0.f, 0.f, 1.f};
4584 colorAtts[1].clearValue = {1.f, 1.f, 1.f, 1.f};
4585 colorAtts[3].clearValue = {0.f, 1.f, 1.f, 1.f};
4586 colorAtts[0].view = slot.normalView.Get();
4587 colorAtts[1].view = slot.depthColorView.Get();
4588 colorAtts[2].view = slot.albedoView.Get();
4589 colorAtts[3].view = slot.pbrParamsView.Get();
4590 colorAtts[4].view = slot.emissiveView.Get();
4591 WGPURenderPassDepthStencilAttachment ds{};
4592 ds.view = slot.depthView.Get();
4593 ds.depthClearValue = 1.f;
4594 ds.depthLoadOp = WGPULoadOp_Clear;
4595 ds.depthStoreOp = WGPUStoreOp_Store;
4596 ds.stencilClearValue = 0;
4597 ds.stencilLoadOp = WGPULoadOp_Undefined;
4598 ds.stencilStoreOp = WGPUStoreOp_Undefined;
4599 WGPURenderPassDescriptor rp{};
4600 rp.colorAttachmentCount = 5;
4601 rp.colorAttachments = colorAtts;
4602 rp.depthStencilAttachment = &ds;
4603 wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(
reinterpret_cast<const wgpu::RenderPassDescriptor*
>(&rp));
4604 flushGbufferPass(pass);
4606 gbufferPassPending =
false;
4607 gbufferDepthValid_ =
true;
4610 ensureAOResources(sceneColorWidth, sceneColorHeight);
4611 if (aoPipeline && aoTex[0]) {
4613 GbufferSlot& gslot = gbufferSlots[currentFrameSlot()];
4620 aou.params = glm::vec4(0.05f, 1.1f, mesh3dNear, mesh3dFar);
4621 aou.intensity = 1.0f;
4622 aou.invScale = 0.5f;
4624 queue.WriteBuffer(aoUbo, 0, &aou,
sizeof(aou));
4625 wgpu::BindGroup aoBg = makeAOBindGroup(gslot.depthView);
4627 WGPURenderPassColorAttachment colorAtt{};
4628 colorAtt.view = aoView[aoWriteIndex].Get();
4629 colorAtt.depthSlice = WGPU_DEPTH_SLICE_UNDEFINED;
4630 colorAtt.loadOp = WGPULoadOp_Clear;
4631 colorAtt.storeOp = WGPUStoreOp_Store;
4632 colorAtt.clearValue = {1.f, 1.f, 1.f, 1.f};
4633 WGPURenderPassDescriptor aoRp{};
4634 aoRp.colorAttachmentCount = 1;
4635 aoRp.colorAttachments = &colorAtt;
4636 wgpu::RenderPassEncoder apass =
4637 encoder.BeginRenderPass(
reinterpret_cast<const wgpu::RenderPassDescriptor*
>(&aoRp));
4638 apass.SetPipeline(aoPipeline);
4639 apass.SetBindGroup(0, aoBg, 0,
nullptr);
4640 apass.SetVertexBuffer(0, fullscreenQuadVb, 0, 4 * 32);
4641 apass.SetIndexBuffer(fullscreenQuadIb, wgpu::IndexFormat::Uint32, 0, 24);
4642 apass.DrawIndexed(6, 1, 0, 0, 0);
4651void Graphics::submitPendingDeferredPasses() {
4652 if ((!gbufferPassPending || gbufferSlots.empty()) && (!decalPassPending || decalSlots.empty()))
return;
4653 auto& uboArena = currentUboArena();
4655 ensureUboArena(uboArena, 4096 + (gbufferPassDraws.size() + decalPassDraws.size()) * 512);
4656 wgpu::CommandEncoder encoder = device.CreateCommandEncoder();
4657 if (gbufferPassPending && !gbufferSlots.empty()) {
4658 lastGbufferSlot = currentFrameSlot();
4659 GbufferSlot& slot = gbufferSlots[lastGbufferSlot];
4660 WGPURenderPassColorAttachment
colors[5]{};
4662 color.depthSlice = WGPU_DEPTH_SLICE_UNDEFINED;
4663 color.loadOp = WGPULoadOp_Clear;
4664 color.storeOp = WGPUStoreOp_Store;
4665 color.clearValue = {0.f, 0.f, 0.f, 0.f};
4667 colors[1].clearValue = {1.f, 1.f, 1.f, 1.f};
4668 colors[3].clearValue = {0.f, 1.f, 1.f, 1.f};
4669 colors[0].view = slot.normalView.Get();
4670 colors[1].view = slot.depthColorView.Get();
4671 colors[2].view = slot.albedoView.Get();
4672 colors[3].view = slot.pbrParamsView.Get();
4673 colors[4].view = slot.emissiveView.Get();
4674 WGPURenderPassDepthStencilAttachment
depth{};
4675 depth.view = slot.depthView.Get();
4676 depth.depthClearValue = 1.f;
4677 depth.depthLoadOp = WGPULoadOp_Clear;
4678 depth.depthStoreOp = WGPUStoreOp_Store;
4679 depth.stencilLoadOp = WGPULoadOp_Undefined;
4680 depth.stencilStoreOp = WGPUStoreOp_Undefined;
4685 wgpu::RenderPassEncoder pass =
4686 encoder.BeginRenderPass(
reinterpret_cast<const wgpu::RenderPassDescriptor*
>(&
descriptor));
4687 flushGbufferPass(pass);
4689 gbufferDepthValid_ =
true;
4691 if (decalPassPending && !decalSlots.empty() && !gbufferSlots.empty()) {
4692 lastDecalSlot = currentFrameSlot();
4693 DecalSlot& slot = decalSlots[lastDecalSlot];
4694 WGPURenderPassColorAttachment
colors[3]{};
4696 color.depthSlice = WGPU_DEPTH_SLICE_UNDEFINED;
4697 color.loadOp = WGPULoadOp_Clear;
4698 color.storeOp = WGPUStoreOp_Store;
4699 color.clearValue = {0.f, 0.f, 0.f, 0.f};
4701 colors[0].view = slot.albedoView.Get();
4702 colors[1].view = slot.normalView.Get();
4703 colors[2].view = slot.paramsView.Get();
4707 wgpu::RenderPassEncoder pass =
4708 encoder.BeginRenderPass(
reinterpret_cast<const wgpu::RenderPassDescriptor*
>(&
descriptor));
4709 flushDecalPass(pass);
4712 wgpu::CommandBuffer command = encoder.Finish();
4713 queue.Submit(1, &command);
4721bool Graphics::acquireSurfaceTexture(wgpu::TextureView&
view, wgpu::Texture& texture) {
4722 if (!surface || !device || !swapchainConfigured)
return false;
4723 wgpu::SurfaceTexture surfTex{};
4724 surface.GetCurrentTexture(&surfTex);
4725 if (!surfTex.texture)
return false;
4726 texture = surfTex.texture;
4727 view = texture.CreateView();
4732#ifdef EVENGINE_WEBGPU
4733 if (device) device.PushErrorScope(wgpu::ErrorFilter::Validation);
4738#ifdef EVENGINE_WEBGPU
4739 if (!device)
return;
4740 device.PopErrorScope(wgpu::CallbackMode::AllowProcessEvents, [](wgpu::PopErrorScopeStatus
status,
4743 if (
message.data &&
type != wgpu::ErrorType::NoError) {
4744#if defined(__EMSCRIPTEN__)
4745 EM_ASM({ console.log(
"[GPU_ERR] type=" + $0 +
" msg=" + UTF8ToString($1)); }, (int)
type,
message.data);
4747 std::fprintf(stderr,
"[webgpu] validation error type=%d: %.*s\n",
int(
type),
4758 if (!device || !surface || !swapchainConfigured)
return;
4760 rebuildSwapchainIfNeeded();
4761 if (!swapchainConfigured)
return;
4764 if (sceneColorWidth > 0 && sceneColorHeight > 0) createSceneColorResources(sceneColorWidth, sceneColorHeight);
4767 wgpu::TextureView surfaceView;
4768 wgpu::Texture surfaceTex;
4769 if (!acquireSurfaceTexture(surfaceView, surfaceTex)) {
4773 auto& uboArena = currentUboArena();
4775 auto& vtxArena = currentVertexArena();
4777 voxelInstanceArena.used = 0;
4778 voxelAoArena.used = 0;
4779 ensureUboArena(uboArena, 4096);
4780 ensureVertexArena(vtxArena, 4096);
4784 hasPendingClear =
false;
4786 wgpu::CommandEncoder encoder = device.CreateCommandEncoder();
4790 recordGpuDrivenCompute(encoder);
4793 if (shadowDepthArray) {
4798 if (mesh3dShadows.
active) {
4800 WGPUTextureViewDescriptor lvd{};
4801 lvd.format = WGPUTextureFormat_Depth32Float;
4802 lvd.dimension = WGPUTextureViewDimension_2D;
4803 lvd.baseMipLevel = 0;
4804 lvd.mipLevelCount = 1;
4805 lvd.baseArrayLayer =
static_cast<uint32_t
>(
c);
4806 lvd.arrayLayerCount = 1;
4807 wgpu::TextureView layerView =
4808 shadowDepthArray->
texture.CreateView(
reinterpret_cast<const wgpu::TextureViewDescriptor*
>(&lvd));
4809 WGPURenderPassDepthStencilAttachment ds{};
4810 ds.view = layerView.Get();
4811 ds.depthClearValue = 1.f;
4812 ds.depthLoadOp = WGPULoadOp_Clear;
4813 ds.depthStoreOp = WGPUStoreOp_Store;
4814 ds.stencilClearValue = 0;
4815 ds.stencilLoadOp = WGPULoadOp_Undefined;
4816 ds.stencilStoreOp = WGPUStoreOp_Undefined;
4817 WGPURenderPassDescriptor rp{};
4818 rp.depthStencilAttachment = &ds;
4819 wgpu::RenderPassEncoder pass =
4820 encoder.BeginRenderPass(
reinterpret_cast<const wgpu::RenderPassDescriptor*
>(&rp));
4821 flushShadowPass(pass,
c);
4829 recordGpuDrivenVisibility(encoder);
4833 const bool wantDeferredLighting = deferredLightingPending_;
4834 if (wantDeferredLighting) flushGBufferPassInto(encoder, aoActive);
4837 wgpu::TextureView sceneView;
4838 wgpu::Texture sceneTex;
4839 if (frameHad3DThisFrame) {
4840 if (active3DCanvas) {
4844 WGPURenderPassColorAttachment colorAtt{};
4845 colorAtt.view = oc->colorView.Get();
4846 colorAtt.depthSlice = WGPU_DEPTH_SLICE_UNDEFINED;
4847 colorAtt.loadOp = WGPULoadOp_Clear;
4848 colorAtt.storeOp = WGPUStoreOp_Store;
4849 colorAtt.clearValue = {clearColor.r, clearColor.g, clearColor.b, 1.f};
4850 WGPURenderPassDepthStencilAttachment ds{};
4851 ds.view = oc->depthView.Get();
4852 ds.depthClearValue = 1.f;
4853 ds.depthLoadOp = WGPULoadOp_Clear;
4854 ds.depthStoreOp = WGPUStoreOp_Store;
4855 ds.stencilClearValue = 0;
4856 ds.stencilLoadOp = WGPULoadOp_Undefined;
4857 ds.stencilStoreOp = WGPUStoreOp_Undefined;
4858 WGPURenderPassDescriptor rp{};
4859 rp.colorAttachmentCount = 1;
4860 rp.colorAttachments = &colorAtt;
4861 rp.depthStencilAttachment = &ds;
4862 wgpu::RenderPassEncoder pass =
4863 encoder.BeginRenderPass(
reinterpret_cast<const wgpu::RenderPassDescriptor*
>(&rp));
4867 flushMesh3D(pass, WGPUTextureFormat_RGBA8Unorm,
true);
4868 flushGpuDrivenDraws(pass,
true);
4869 flushPrimitive3D(pass, WGPUTextureFormat_RGBA8Unorm, 1);
4874 lastReadbackTex = oc->color;
4877 }
else if (!sceneColorSlots.empty()) {
4878 SceneColorSlot& slot = sceneColorSlots[currentFrameSlot()];
4879 std::vector<Mesh3dDraw> refractiveDraws;
4880 const bool splitSceneColor = mesh3dSceneColorCaptureIndex.has_value() &&
4881 *mesh3dSceneColorCaptureIndex <= mesh3dDraws.size() &&
4882 sceneColorSlots.size() > 1 && slot.sampleCount == 1;
4883 if (splitSceneColor) {
4884 auto split = mesh3dDraws.begin() +
static_cast<std::ptrdiff_t
>(*mesh3dSceneColorCaptureIndex);
4885 refractiveDraws.assign(std::make_move_iterator(
split), std::make_move_iterator(mesh3dDraws.end()));
4886 mesh3dDraws.erase(
split, mesh3dDraws.end());
4888 WGPURenderPassColorAttachment colorAtt{};
4889 colorAtt.view = slot.sampleCount > 1 ? slot.msaaView.Get() : slot.colorView.Get();
4890 colorAtt.resolveTarget = slot.sampleCount > 1 ? slot.colorView.Get() :
nullptr;
4891 colorAtt.depthSlice = WGPU_DEPTH_SLICE_UNDEFINED;
4892 colorAtt.loadOp = WGPULoadOp_Clear;
4895 colorAtt.storeOp = slot.sampleCount > 1 ? WGPUStoreOp_Discard : WGPUStoreOp_Store;
4896 colorAtt.clearValue = {clearColor.r, clearColor.g, clearColor.b, 1.f};
4897 WGPURenderPassDepthStencilAttachment ds{};
4898 ds.view = slot.depthView.Get();
4899 ds.depthClearValue = 1.f;
4900 ds.depthLoadOp = WGPULoadOp_Clear;
4901 ds.depthStoreOp = WGPUStoreOp_Store;
4902 ds.stencilClearValue = 0;
4903 ds.stencilLoadOp = WGPULoadOp_Undefined;
4904 ds.stencilStoreOp = WGPUStoreOp_Undefined;
4905 WGPURenderPassDescriptor rp{};
4906 rp.colorAttachmentCount = 1;
4907 rp.colorAttachments = &colorAtt;
4908 rp.depthStencilAttachment = &ds;
4909 wgpu::RenderPassEncoder pass =
4910 encoder.BeginRenderPass(
reinterpret_cast<const wgpu::RenderPassDescriptor*
>(&rp));
4913 if (wantDeferredLighting) flushDeferredLighting(pass);
4914 flushVoxelDraws(pass, sceneColorFormat);
4915 flushGpuDrivenResolve(pass);
4916 flushMesh3D(pass, sceneColorFormat);
4917 flushGpuDrivenDraws(pass,
false);
4918 flushPrimitive3D(pass, sceneColorFormat, slot.sampleCount);
4920 if (splitSceneColor) {
4921 const uint32_t snapshotIndex =
4922 (currentFrameSlot() + 1u) %
static_cast<uint32_t
>(sceneColorSlots.size());
4923 SceneColorSlot& snapshot = sceneColorSlots[snapshotIndex];
4924 WGPUTexelCopyTextureInfo
source{};
4925 source.texture = slot.color.Get();
4927 source.origin = {0, 0, 0};
4928 source.aspect = WGPUTextureAspect_All;
4929 WGPUTexelCopyTextureInfo destination{};
4930 destination.texture = snapshot.color.Get();
4931 destination.mipLevel = 0;
4932 destination.origin = {0, 0, 0};
4933 destination.aspect = WGPUTextureAspect_All;
4934 WGPUExtent3D extent{
static_cast<uint32_t
>(sceneColorWidth),
static_cast<uint32_t
>(sceneColorHeight), 1};
4935 encoder.CopyTextureToTexture(
reinterpret_cast<const wgpu::TexelCopyTextureInfo*
>(&
source),
4936 reinterpret_cast<const wgpu::TexelCopyTextureInfo*
>(&destination),
4937 reinterpret_cast<const wgpu::Extent3D*
>(&extent));
4939 lastPresentSlot = snapshotIndex;
4940 sceneColorHistoryValid =
true;
4941 mesh3dDraws = std::move(refractiveDraws);
4942 colorAtt.resolveTarget =
nullptr;
4943 colorAtt.loadOp = WGPULoadOp_Load;
4944 colorAtt.storeOp = WGPUStoreOp_Store;
4945 ds.depthLoadOp = WGPULoadOp_Load;
4946 wgpu::RenderPassEncoder transparentPass =
4947 encoder.BeginRenderPass(
reinterpret_cast<const wgpu::RenderPassDescriptor*
>(&rp));
4948 flushMesh3D(transparentPass, sceneColorFormat);
4949 transparentPass.End();
4951 mesh3dSceneColorCaptureIndex.reset();
4952 lastPresentSlot = currentFrameSlot();
4953 sceneColorHistoryValid =
true;
4954 lastReadbackTex = slot.color;
4955 lastReadbackW = sceneColorWidth;
4956 lastReadbackH = sceneColorHeight;
4957 sceneView = slot.colorView;
4958 sceneTex = slot.color;
4963 if (!wantDeferredLighting) flushGBufferPassInto(encoder, aoActive);
4967 if (decalPassPending && !decalSlots.empty() && !gbufferSlots.empty()) {
4968 lastDecalSlot = currentFrameSlot();
4969 DecalSlot& slot = decalSlots[lastDecalSlot];
4970 WGPURenderPassColorAttachment colorAtts[3]{};
4971 for (
auto& attachment : colorAtts) {
4972 attachment.depthSlice = WGPU_DEPTH_SLICE_UNDEFINED;
4973 attachment.loadOp = WGPULoadOp_Clear;
4974 attachment.storeOp = WGPUStoreOp_Store;
4975 attachment.clearValue = {0.f, 0.f, 0.f, 0.f};
4977 colorAtts[0].view = slot.albedoView.Get();
4978 colorAtts[1].view = slot.normalView.Get();
4979 colorAtts[2].view = slot.paramsView.Get();
4980 WGPURenderPassDescriptor rp{};
4981 rp.colorAttachmentCount = 3;
4982 rp.colorAttachments = colorAtts;
4983 wgpu::RenderPassEncoder pass =
4984 encoder.BeginRenderPass(
reinterpret_cast<const wgpu::RenderPassDescriptor*
>(&rp));
4985 flushDecalPass(pass);
4997 WGPURenderPassColorAttachment colorAtt{};
4998 colorAtt.view = surfaceView.Get();
4999 colorAtt.depthSlice = WGPU_DEPTH_SLICE_UNDEFINED;
5000 colorAtt.loadOp = WGPULoadOp_Clear;
5001 colorAtt.storeOp = WGPUStoreOp_Store;
5002 colorAtt.clearValue = {clearColor.r, clearColor.g, clearColor.b, 1.f};
5003 WGPURenderPassDescriptor rp{};
5004 rp.colorAttachmentCount = 1;
5005 rp.colorAttachments = &colorAtt;
5006 wgpu::RenderPassEncoder pass =
5007 encoder.BeginRenderPass(
reinterpret_cast<const wgpu::RenderPassDescriptor*
>(&rp));
5009 if (sceneView && !sceneColorComposited) {
5010 if (!fullscreenQuadReady) {
5013 -1.f, -1.f, 1.f, 1.f, 1.f, 1.f, 0.f, 0.f, 1.f, -1.f, 1.f, 1.f, 1.f, 1.f, 1.f, 0.f,
5014 1.f, 1.f, 1.f, 1.f, 1.f, 1.f, 1.f, 1.f, -1.f, 1.f, 1.f, 1.f, 1.f, 1.f, 0.f, 1.f,
5016 uint32_t
indices[6] = {0, 1, 2, 2, 3, 0};
5017 WGPUBufferDescriptor vbd{};
5018 vbd.label =
sv(
"eve_fullscreen_vb");
5019 vbd.size =
sizeof(
verts);
5020 vbd.usage = WGPUBufferUsage_CopyDst | WGPUBufferUsage_Vertex;
5021 fullscreenQuadVb = device.CreateBuffer(
reinterpret_cast<const wgpu::BufferDescriptor*
>(&vbd));
5022 queue.WriteBuffer(fullscreenQuadVb, 0,
verts,
sizeof(
verts));
5023 WGPUBufferDescriptor ibd{};
5024 ibd.label =
sv(
"eve_fullscreen_ib");
5026 ibd.usage = WGPUBufferUsage_CopyDst | WGPUBufferUsage_Index;
5027 fullscreenQuadIb = device.CreateBuffer(
reinterpret_cast<const wgpu::BufferDescriptor*
>(&ibd));
5028 queue.WriteBuffer(fullscreenQuadIb, 0,
indices,
sizeof(
indices));
5029 fullscreenQuadReady =
true;
5034 sceneGpu.
view = sceneView;
5035 sceneGpu.
sampler = createLinearSampler(device);
5036 wgpu::BindGroup bg = makeTex2DBindGroup(&sceneGpu,
nullptr);
5037 uint32_t offsets[1] = {0};
5039 pass.SetBindGroup(0, bg, 1, offsets);
5040 pass.SetVertexBuffer(0, fullscreenQuadVb, 0, 4 * 32);
5041 pass.SetIndexBuffer(fullscreenQuadIb, wgpu::IndexFormat::Uint32, 0, 24);
5042 pass.DrawIndexed(6, 1, 0, 0, 0);
5045 if (!activeCanvas) {
5046 flush2D(pass, pixelW > 0 ? pixelW : logicalW, pixelH > 0 ? pixelH : logicalH, surfaceFormat);
5050 WGPURenderPassEncoder cPass = pass.Get();
5057 wgpu::CommandBuffer cmd = encoder.Finish();
5058 queue.Submit(1, &cmd);
5063#ifdef __EMSCRIPTEN__
5064 if (instance) instance.ProcessEvents();
5071#if !defined(__EMSCRIPTEN__)
5076 frame3DStarted =
false;
5077 frameHad3DThisFrame =
false;
5079 sceneColorPassOpen =
false;
5080 active3DCanvas =
nullptr;
5081 gbufferPassPending =
false;
5082 deferredLightingPending_ =
false;
5091 ownedCanvases.push_back(std::unique_ptr<eve::graphics::Canvas>(
c));
5097 ownedCanvases.push_back(std::unique_ptr<eve::graphics::Canvas>(
c));
5102 if (activeCanvas && canvas != activeCanvas) {
5105 activeCanvas = canvas;
5112 if (!canvas || !canvas->
getTexture())
return;
5113 auto& uboArena = currentUboArena();
5115 auto& vtxArena = currentVertexArena();
5118 wgpu::CommandEncoder enc = device.CreateCommandEncoder();
5119 WGPURenderPassColorAttachment ca{};
5120 ca.view = canvas->colorView.Get();
5121 ca.depthSlice = WGPU_DEPTH_SLICE_UNDEFINED;
5122 ca.loadOp = canvas->
clearRequested ? WGPULoadOp_Clear : WGPULoadOp_Load;
5123 ca.storeOp = WGPUStoreOp_Store;
5125 WGPURenderPassDescriptor rp{};
5126 rp.colorAttachmentCount = 1;
5127 rp.colorAttachments = &ca;
5128 wgpu::RenderPassEncoder pass = enc.BeginRenderPass(
reinterpret_cast<const wgpu::RenderPassDescriptor*
>(&rp));
5130 canvas->
isHDR() ? WGPUTextureFormat_RGBA16Float : WGPUTextureFormat_RGBA8Unorm);
5132 wgpu::CommandBuffer cmd = enc.Finish();
5133 queue.Submit(1, &cmd);
5138 if (activeCanvas)
return activeCanvas->
getTexture();
5139 return sceneColorTexture;
5153 if (
frameHad3D && activeCanvas ==
nullptr)
return;
5155 hasPendingClear =
true;
5158 canvas->clear(clearColor, std::nullopt, std::nullopt);
5168wgpu::RenderPipeline buildPipelineFromWgsl(wgpu::Device& dev, wgpu::PipelineLayout
layout, WGPUTextureFormat format,
5170 bool mesh3d,
bool hair,
bool shadow,
bool gbuffer, uint32_t sampleCount,
5171 bool depthWrite =
true) {
5172 WGPURenderPipelineDescriptor pd{};
5173 pd.label =
sv(
"eve_custom_shader");
5174 pd.layout =
layout.Get();
5176 WGPUVertexAttribute attrs[6]{};
5177 WGPUVertexBufferLayout vb{};
5178 if (mesh3d || shadow || gbuffer) {
5180 attrs[0].format = WGPUVertexFormat_Float32x3;
5181 attrs[0].offset = 0;
5182 attrs[0].shaderLocation = 0;
5183 attrs[1].format = WGPUVertexFormat_Float32x3;
5184 attrs[1].offset = 12;
5185 attrs[1].shaderLocation = 1;
5186 attrs[2].format = WGPUVertexFormat_Float32x2;
5187 attrs[2].offset = 24;
5188 attrs[2].shaderLocation = 2;
5189 vb.arrayStride =
sizeof(MeshVertex);
5190 vb.stepMode = WGPUVertexStepMode_Vertex;
5191 vb.attributeCount = 6;
5192 vb.attributes = attrs;
5194 attrs[0].format = WGPUVertexFormat_Float32x2;
5195 attrs[0].offset = 0;
5196 attrs[0].shaderLocation = 0;
5197 attrs[1].format = WGPUVertexFormat_Float32x4;
5198 attrs[1].offset = 8;
5199 attrs[1].shaderLocation = 1;
5200 attrs[2].format = WGPUVertexFormat_Float32x2;
5201 attrs[2].offset = 24;
5202 attrs[2].shaderLocation = 2;
5203 vb.arrayStride = 32;
5204 vb.stepMode = WGPUVertexStepMode_Vertex;
5205 vb.attributeCount = 3;
5206 vb.attributes =
attrs;
5208 pd.vertex.bufferCount = 1;
5209 pd.vertex.buffers = &vb;
5214 wgpu::ShaderModule vertexModule;
5215 wgpu::ShaderModule fragmentModule;
5216 if (!
vert.empty()) {
5217 WGPUShaderModuleDescriptor md = mdDesc(
vert);
5218 vertexModule = dev.CreateShaderModule(
reinterpret_cast<const wgpu::ShaderModuleDescriptor*
>(&md));
5219 pd.vertex.module = vertexModule.Get();
5220 pd.vertex.entryPoint =
sv(
"vs_main");
5224 vertexModule = dev.CreateShaderModule(
reinterpret_cast<const wgpu::ShaderModuleDescriptor*
>(&md));
5225 pd.vertex.module = vertexModule.Get();
5226 pd.vertex.entryPoint =
sv(
"vs_main");
5229 if (!
frag.empty()) {
5230 WGPUFragmentState fs{};
5231 WGPUShaderModuleDescriptor md = mdDesc(
frag);
5232 fragmentModule = dev.CreateShaderModule(
reinterpret_cast<const wgpu::ShaderModuleDescriptor*
>(&md));
5233 fs.module = fragmentModule.Get();
5234 fs.entryPoint =
sv(
"fs_main");
5236 WGPUColorTargetState
target{};
5238 target.writeMask = WGPUColorWriteMask_All;
5240 static WGPUBlendState bs = alphaBlend();
5246 pd.fragment =
nullptr;
5249 pd.primitive.topology = WGPUPrimitiveTopology_TriangleList;
5250 pd.primitive.frontFace = WGPUFrontFace_CCW;
5251 pd.primitive.cullMode =
hair ? WGPUCullMode_None : WGPUCullMode_None;
5252 pd.primitive.stripIndexFormat = WGPUIndexFormat_Undefined;
5254 if (
depth && !shadow) {
5255 static WGPUDepthStencilState ds{};
5256 ds.format = WGPUTextureFormat_Depth32Float;
5257 ds.depthWriteEnabled = depthWrite ? WGPUOptionalBool_True : WGPUOptionalBool_False;
5258 ds.depthCompare = WGPUCompareFunction_Less;
5259 pd.depthStencil = &ds;
5261 pd.multisample.count = sampleCount ? sampleCount : 1;
5263 pd.multisample.mask = 0xFFFFFFFFu;
5264 return dev.CreateRenderPipeline(
reinterpret_cast<const wgpu::RenderPipelineDescriptor*
>(&pd));
5270 if (!device)
throw Exception(
"newShaderFromSpv: device not initialized");
5275 "newShaderFromSpv: SPIR-V custom shaders are not supported on the "
5276 "WebGPU backend (browsers accept WGSL only). Recompile the shader "
5277 "with the WebGPU toolchain (glslc+tint) and use newShaderFromWgsl.");
5282 "newShaderFromSpvFile: SPIR-V custom shaders are not supported on the "
5283 "WebGPU backend. Use WGSL shaders instead.");
5290 auto gpuIt = std::find_if(ownedGpuShaders.begin(), ownedGpuShaders.end(),
5291 [&](
const std::unique_ptr<GpuShader>&
g) { return g.get() == gpu; });
5292 if (gpuIt == ownedGpuShaders.end())
return false;
5294 auto shIt = std::find_if(ownedShaders.begin(), ownedShaders.end(),
5295 [&](
const std::unique_ptr<Shader>&
s) { return s.get() == shader; });
5296 if (shIt == ownedShaders.end())
return false;
5298 shader->gpuHandle =
nullptr;
5299 ownedGpuShaders.erase(gpuIt);
5301 (void)shIt->release();
5302 ownedShaders.erase(shIt);
5310 "newShader: runtime GLSL compilation is not available on the WebGPU "
5311 "backend (browser WGSL only). Ship pre-compiled WGSL shaders.");
5318 "newMeshShaderFromSpv: SPIR-V custom mesh shaders are not supported on the "
5319 "WebGPU backend. Use WGSL shaders instead.");
5323 if (!device)
throw Exception(
"newMeshShaderFromWgsl: device not initialized");
5324 if (!mesh3dSetLayout)
throw Exception(
"newMeshShaderFromWgsl: mesh3d layout missing");
5329 auto gpu = std::make_unique<GpuShader>();
5330 gpu->isMesh3D =
true;
5331 gpu->wgslVert =
vert;
5332 gpu->wgslFrag =
frag;
5333 gpu->mesh3dPipeline = buildPipelineFromWgsl(device, mesh3dPipelineLayout, sceneColorFormat,
vert,
frag,
5334 true,
false,
true,
false,
5339 gpu->mesh3dXrayPipeline = buildPipelineFromWgsl(device, mesh3dPipelineLayout, sceneColorFormat,
vert,
frag,
5340 false,
true,
true,
false,
5344 auto sh = std::make_unique<Shader>();
5346 sh->gpuHandle = gpu.get();
5349 ownedShaders.push_back(std::move(sh));
5350 ownedGpuShaders.push_back(std::move(gpu));
5356 auto found = std::find_if(ownedGpuShaders.begin(), ownedGpuShaders.end(),
5357 [&](
const auto& gpu) { return gpu.get() == shader.gpuHandle; });
5358 if (
found == ownedGpuShaders.end() || !(*found)->isMesh3D)
5361 auto& gpu = **
found;
5363 gpu.mesh3dPipeline =
5364 buildPipelineFromWgsl(device, mesh3dPipelineLayout, sceneColorFormat, gpu.wgslVert, gpu.wgslFrag,
true,
5365 alphaBlend,
true,
doubleSided,
false,
false, sceneColorSamples, depthWrite);
5367 shader.meshDepthWrite = depthWrite;
5376 "newMeshShader: runtime GLSL compilation is not available on the WebGPU "
5377 "backend. Ship pre-compiled WGSL shaders.");
5381 if (!device)
throw Exception(
"newShaderFromWgsl: device not initialized");
5382 if (fragWgsl.empty())
throw Exception(
"newShaderFromWgsl: empty fragment WGSL");
5383 if (!tex2DPipelineLayout)
throw Exception(
"newShaderFromWgsl: 2D pipeline layout missing");
5387 auto gpu = std::make_unique<GpuShader>();
5388 gpu->isMesh3D =
false;
5389 gpu->wgslVert =
vert;
5390 gpu->wgslFrag = fragWgsl;
5393 gpu->swapchainPipeline = createPipelineForShader(gpu.get(), wgpu::TextureFormat(surfaceFormat),
5396 false, tex2DPipelineLayout);
5397 gpu->offscreenPipeline = createPipelineForShader(gpu.get(), wgpu::TextureFormat::RGBA8Unorm,
5400 false, tex2DPipelineLayout);
5401 gpu->swapchainOpaquePipeline = buildPipelineFromWgsl(device, tex2DPipelineLayout, WGPUTextureFormat(surfaceFormat),
5402 vert, fragWgsl,
false,
false,
false,
false,
false,
false, 1u);
5403 gpu->offscreenOpaquePipeline = buildPipelineFromWgsl(device, tex2DPipelineLayout, WGPUTextureFormat_RGBA8Unorm,
5404 vert, fragWgsl,
false,
false,
false,
false,
false,
false, 1u);
5405 gpu->hdrOffscreenPipeline = createPipelineForShader(gpu.get(), wgpu::TextureFormat::RGBA16Float,
false,
false,
5406 false,
false,
false, tex2DPipelineLayout);
5407 gpu->hdrOffscreenOpaquePipeline =
5408 buildPipelineFromWgsl(device, tex2DPipelineLayout, WGPUTextureFormat_RGBA16Float,
vert, fragWgsl,
false,
false,
5409 false,
false,
false,
false, 1u);
5411 auto sh = std::make_unique<Shader>();
5413 sh->gpuHandle = gpu.get();
5416 ownedShaders.push_back(std::move(sh));
5417 ownedGpuShaders.push_back(std::move(gpu));
5421wgpu::RenderPipeline Graphics::createPipelineForShader(
GpuShader* gs, wgpu::TextureFormat format,
bool depth,
5422 bool mesh3d,
bool hair,
bool shadow,
bool gbuffer,
5423 wgpu::PipelineLayout
layout) {
5424 if (!gs || gs->
wgslFrag.empty())
return {};
5428 const uint32_t samples = mesh3d ||
hair || shadow || gbuffer ? sceneColorSamples : 1u;
5430 mesh3d, hair, shadow, gbuffer, samples);
5437 "newHairShaderFromSpv: SPIR-V hair shaders are not supported on WebGPU; "
5438 "use newHairShaderFromWgsl");
5442 if (!device)
throw Exception(
"newHairShaderFromWgsl: device not initialized");
5443 if (fragWgsl.empty())
throw Exception(
"newHairShaderFromWgsl: empty fragment WGSL");
5444 auto gpu = std::make_unique<GpuShader>();
5445 gpu->isMesh3D =
true;
5446 gpu->isHair3D =
true;
5448 gpu->wgslFrag = fragWgsl;
5449 gpu->mesh3dPipeline = buildPipelineFromWgsl(device, mesh3dPipelineLayout, sceneColorFormat, gpu->wgslVert,
5450 gpu->wgslFrag,
true,
true,
true,
true,
false,
false, sceneColorSamples);
5451 auto shader = std::make_unique<Shader>();
5453 shader->gpuHandle = gpu.get();
5455 ownedShaders.push_back(std::move(
shader));
5456 ownedGpuShaders.push_back(std::move(gpu));
std::vector< QuestEvent > pending
std::unordered_map< std::string, QuestRuntime > entries
std::vector< std::uint32_t > verts
building::EdgeCurveGroup group
graphics::Texture * albedo
std::uint32_t vertexCount
vk::UniqueSampler sampler
wgpu::PopErrorScopeStatus status
std::vector< std::uint32_t > indices
std::array< float, 3 > scale
std::vector< float > colors
const UnitySourceAsset & source
std::vector< double > phi
static Diagnostic error(DiagnosticCode code, std::string message, std::string path={}, DiagnosticDetails details={}, std::string source={})
Construct an error diagnostic with the standard error severity.
EVENGINE_API_FOUNDATION public API.
eve::Result< ResourcePin > pin(Resource *resource)
Keeps one cached resource alive across unload()/clear()/reload.
ResultRef< Resource > waitFor(std::string key)
Block until key is cached, failed, or unclaimed.
static ResourceManager & getInstance()
Returns the instance.
Move-only operation result carrying either a value or Status.
static Result success(T value)
Construct a successful result owning value.
static Result failure(Status status)
Construct a failed result from a structured status.
Accumulates solid / textured quads in logical (Y-down) coordinates. Used by RenderSystem; not a publi...
virtual int getWidth() const =0
Returns the width.
virtual Texture * getTexture()=0
Sampleable color buffer; screen Canvas returns nullptr.
virtual int getHeight() const =0
Returns the height.
Screen-space buffers for mid/post effects and Hybrid deferred lighting.
Texture * getDepthTexture() const
Returns the depth texture.
void setTargets(int width, int height, Texture *depth, Texture *normal, Texture *albedo, Texture *hwDepth=nullptr, Texture *pbrParams=nullptr, Texture *emissive=nullptr)
Called by Graphics after a G-buffer pass (or clear).
bool isValid() const
True when valid.
bool screenReadbackEnabled
virtual void setVSync(bool enabled)
Prefer uncapped present (IMMEDIATE/MAILBOX) when false, vsync (MAILBOX/FIFO) when true....
int getWidth() const
Returns the width.
std::vector< DeferredFileTexture > deferredFileTextures_
void retireResourceLifetime() const
Retire resource lifetime.
bool fileTextureSourceExists(const std::string &filename) const
File texture source exists.
void ensureFileTexturesReady()
Finish CPU decode and GPU upload for outstanding newTextureFromFile results. @thread Game/render thre...
void setCanvas()
Sets the canvas.
void * presentOverlayUser_
std::unique_ptr< RenderControl > renderControl_
void requestFileImageDecode(const std::string &key)
Request file image decode.
Mesh3DSceneColorCaptureStatus
Observable result of requesting a same-frame mesh SceneColor snapshot.
RenderControl * getRenderControl()
Shared compilable 3D render control (features → pass list + GBuffer). Owned by Graphics; valid for th...
PresentOverlayFn presentOverlayFn_
void dropDeferredFileTexture(Texture *texture)
Drop deferred file texture.
int getHeight() const
Returns the height.
GPU mesh handle (+ optional CPU morph targets).
Frame-local Skia-style recorder for 2D line primitives.
GBuffer * getGBuffer()
Returns the g buffer.
static constexpr uint32_t kPushConstantBytes
GPU texture created via Graphics::newTexture. Owns GPU resources through an opaque backend handle.
void setMesh3DLodDither(float weight, bool reverse, bool enabled) override
Sets the mesh 3 d lod dither.
void pushValidationScope() override
Pushes validation scope.
Texture * newCubemap(int faceSize, const uint8_t *rgbaFaces) override
Creates a cubemap. @ownership Caller deletes unless documented otherwise.
void setMesh3DEnv(Texture *cube, float intensity) override
Sets the mesh 3 d env.
void setMesh3DLight(const glm::vec3 &dir, const glm::vec3 &color) override
Sets the mesh 3 d light.
void setMesh3DPackedNormalMask(bool enabled) override
Sets the mesh 3 d packed normal mask.
void setMesh3DReflectionProbes(const ReflectionProbeUpload &upload) override
Sets the mesh 3 d reflection probes.
void drawTexturedRectLitUVRotated(Texture *albedo, Texture *normal, float cx, float cy, float w, float h, float degrees, float u0, float v0, float u1, float v1, const Color &color, BlendMode blend=BlendMode::Alpha) override
Draws textured rect lit uv rotated.
void present() override
Present.
Shader * newMeshShader(const std::string &vertGlsl, const std::string &fragGlsl) override
Creates a mesh shader. @ownership Caller deletes unless documented otherwise.
void flush2DToCanvas(OffscreenCanvas *canvas)
Flush accumulated 2D batches into an offscreen canvas target.
Mesh3DSceneColorCaptureStatus captureMesh3DSceneColor() override
Capture mesh 3 d scene color.
void setMesh3DViewProj(const glm::mat4 &viewProj) override
Sets the mesh 3 d view proj.
void draw(eve::graphics::Graphics *gfx, const glm::mat4 &matrix) const override
Draws .
void endDecalPass() override
Ends decal pass.
void setMesh3DShadowReceive(bool receive) override
Sets the mesh 3 d shadow receive.
void pumpReadback()
Advances a pending frame readback; called every present().
Shader * newShaderFromSpvFile(const std::string &vertPath, const std::string &fragPath) override
Creates a shader from spv file. @ownership Caller deletes unless documented otherwise.
void setMsaaSamples(int samples) override
Sets the msaa samples.
float getMaxAnisotropy() const override
Returns the max anisotropy.
ResultRef< Texture > newSharedTexture(image::ImageData *data, const std::string &contentKey) override
Reuse immutable imported pixels. Graphics owns the returned texture until release or shutdown.
Mesh * newMeshSphere(int slices=32, int stacks=16) override
Creates a mesh sphere. @ownership Caller deletes unless documented otherwise.
void popValidationScope() override
Pops validation scope.
void clear(std::optional< Color > color, std::optional< int > stencil, std::optional< double > depth) override
Clears .
void setMesh3DNormalTexture(Texture *normal) override
Sets the mesh 3 d normal texture.
Mesh * newMeshFromArraysColored(const float *posXYZ, const float *nrmXYZ, const float *uvST, const float *colorRGBA, int vertexCount, const uint32_t *indices, int indexCount) override
Create a mesh with a packed linear RGBA vertex-color stream.
void setMesh3DHeightTexture(Texture *height) override
Sets the mesh 3 d height texture.
void begin3DFrame() override
Begins 3 d frame.
Mesh * newMeshFromAssimp(const ::aiMesh &mesh) override
Creates a mesh from assimp. @ownership Caller deletes unless documented otherwise.
void drawTexturedRectLitUV(Texture *albedo, Texture *normal, float x, float y, float w, float h, float u0, float v0, float u1, float v1, const Color &color, BlendMode blend=BlendMode::Alpha) override
Draws textured rect lit uv.
eve::Result< void > updateTextureRegions(Texture *texture, std::span< const TextureRegionUpload > regions) override
Updates texture regions.
std::optional< eve::graphics::MeshBackendDescriptor > describeMesh(Mesh *mesh) const override
Return layout facts from the owned WebGPU mesh upload.
bool uploadDeferredFileTexture(Texture *texture, image::ImageData *data) override
Uploads deferred file texture.
void setMesh3DCameraPos(const glm::vec3 &eye) override
Sets the mesh 3 d camera pos.
void beginGBufferPass(int width, int height) override
Begins g buffer pass.
void setMesh3DVirtualTexture(bool enabled, int pageCountX, int pageCountY, int atlasSlotsX, int atlasSlotsY, float borderFraction) override
Sets the mesh 3 d virtual texture.
Shader * newMeshShaderFromWgsl(const std::string &vertWgsl, const std::string &fragWgsl) override
Creates a mesh shader from wgsl. @ownership Caller deletes unless documented otherwise.
Shader * newHairShaderFromWgsl(const std::string &vertWgsl, const std::string &fragWgsl) override
Creates a hair shader from wgsl. @ownership Caller deletes unless documented otherwise.
void drawMeshGBufferAlpha(Mesh *mesh, const glm::mat4 &mvp, const glm::mat4 &model, float nearZ, float farZ, Texture *albedo=nullptr, float tintR=1.f, float tintG=1.f, float tintB=1.f, float motionX=0.f, float motionY=0.f, float roughness=0.45f, float metallic=0.f) override
Draws mesh g buffer alpha.
bool bakeMeshMorph(Mesh *mesh) override
If mesh morph weights are dirty, bake blended positions and upload to the GPU VBO....
void setMesh3DEnvProbe(const glm::vec3 ¢er, const glm::vec3 &extent) override
Sets the mesh 3 d env probe.
Texture * newTextureFromFile(const std::string &filename) override
Creates a texture from file. @ownership Caller deletes unless documented otherwise.
void drawTexturedRectShader5(Texture *color, Texture *depth, Texture *motion, Texture *extra, Texture *specular, Shader *shader, float x, float y, float w, float h, const Color &tint) override
Draws textured rect shader 5.
void setViewportSize(int width, int height, int pixelwidth, int pixelheight) override
Sets the viewport size.
void setMesh3DLighting(const Lighting3DPack &pack) override
Sets the mesh 3 d lighting.
void drawMeshShader(Mesh *mesh, const glm::mat4 &model, Texture *texture, const Color &tint, Shader *shader) override
Draws mesh shader.
void setMesh3DMaterial(float metallic, float roughness) override
Sets the mesh 3 d material.
void drawSolidRectRotated(float cx, float cy, float w, float h, float degrees, const Color &color, BlendMode blend=BlendMode::Alpha) override
Draws solid rect rotated.
void setMesh3DSkinInfluenceLimit(SkinInfluenceLimit count) override
Sets the mesh 3 d skin influence limit.
void setLighting2D(const Lighting2DUBO &ubo) override
Sets the lighting 2 d.
void endGBufferPass() override
Ends g buffer pass.
void setMesh3DShadows(const ShadowUpload &upload) override
Sets the mesh 3 d shadows.
friend class OffscreenCanvas
Shader * newMeshShaderFromSpv(const std::vector< uint32_t > &vertSpv, const std::vector< uint32_t > &fragSpv) override
Creates a mesh shader from spv. @ownership Caller deletes unless documented otherwise.
Texture * newTexture(int width, int height, const uint8_t *rgba, bool repeatU=false, bool repeatV=false) override
Creates a texture. @ownership Caller deletes unless documented otherwise.
void end3DFrameToCanvas() override
Ends 3 d frame to canvas.
bool copyHDRCanvasesToCubemap(Canvas *const *sources, int faceCount, Texture *cubemap) override
Copies hdr canvases to cubemap.
void drawTexturedRectShader4(Texture *color, Texture *depth, Texture *motion, Texture *extra, Shader *shader, float x, float y, float w, float h, const Color &tint) override
Draws textured rect shader 4.
bool updateMeshVertices(Mesh *mesh, const float *posXYZ, const float *nrmXYZ, const float *uvST, int vertexCount, const uint32_t *indices, int indexCount) override
Updates mesh vertices.
void drawMesh(Mesh *mesh, const glm::mat4 &model, Texture *texture, const Color &tint) override
Draws mesh.
Shader * newHairShaderFromSpv(const std::vector< uint32_t > &vertSpv, const std::vector< uint32_t > &fragSpv) override
Creates a hair shader from spv. @ownership Caller deletes unless documented otherwise.
Shader * newShaderFromSpv(const std::vector< uint32_t > &vertSpv, const std::vector< uint32_t > &fragSpv) override
Creates a shader from spv. @ownership Caller deletes unless documented otherwise.
void setMesh3DView(const glm::mat4 &view) override
Sets the mesh 3 d view.
void drawTexturedRect(Texture *texture, float x, float y, float w, float h, const Color &color) override
Draws textured rect.
void begin3DFrameToCanvas(Canvas *canvas) override
Begins 3 d frame to canvas.
void drawMeshGBuffer(Mesh *mesh, const glm::mat4 &mvp, const glm::mat4 &model, float nearZ, float farZ, Texture *albedo=nullptr, float tintR=1.f, float tintG=1.f, float tintB=1.f, float motionX=0.f, float motionY=0.f, float roughness=0.45f, float metallic=0.f) override
Draws mesh g buffer.
void drawTexturedRectUV(Texture *texture, float x, float y, float w, float h, float u0, float v0, float u1, float v1, const Color &color) override
Draws textured rect uv.
bool releaseShader(Shader *shader) override
Release shader.
void setMesh3DClusteredLighting(const ClusteredLightingUpload &upload) override
Sets the mesh 3 d clustered lighting.
void setMesh3DTexCellBomb(float cellScale, float strength, float rotAmount=1.f) override
Sets the mesh 3 d tex cell bomb.
void initWithWindow(void *nativeWindow) override
Initializes with window.
void setMesh3DSceneColor(Texture *color) override
Sets the mesh 3 d scene color.
~Graphics() override
Graphics.
Mesh * newMeshCylinder(int slices=32, int stacks=1, bool caps=true) override
Creates a mesh cylinder. @ownership Caller deletes unless documented otherwise.
eve::Result< void > configureMeshShaderSurface(Shader &shader, BlendMode blend, bool depthWrite, bool doubleSided) override
Configure mesh shader surface.
void beginDecalPass(int width, int height) override
Begins decal pass.
void drawTexturedRectShaderUVRotated(Texture *texture, Shader *shader, float cx, float cy, float w, float h, float degrees, float u0, float v0, float u1, float v1, const Color &color, bool rotatedUV=false, BlendMode blend=BlendMode::Alpha) override
Draws textured rect shader uv rotated.
void setMesh3DParallax(float scale, float minLayers=8.f, float maxLayers=32.f) override
Sets the mesh 3 d parallax.
void drawTexturedRectShaderDepthMotion(Texture *color, Texture *depth, Texture *motion, Shader *shader, float x, float y, float w, float h, const Color &tint) override
Draws textured rect shader depth motion.
Mesh * newMeshFromArrays(const float *posXYZ, const float *nrmXYZ, const float *uvST, int vertexCount, const uint32_t *indices, int indexCount) override
Creates a mesh from arrays. @ownership Caller deletes unless documented otherwise.
Shader * newShader(const std::string &vertGlsl, const std::string &fragGlsl) override
Creates a shader. @ownership Caller deletes unless documented otherwise.
Texture * getSceneLinearDepthTexture() override
Return the active sampleable scene linear-depth texture when available. @ownership Borrowed from the ...
void drawMeshShadow(Mesh *mesh, const glm::mat4 &lightMVP, bool doubleSided=true) override
Draws mesh shadow.
void setMesh3DClusteredActive(bool active) override
Sets the mesh 3 d clustered active.
bool copyHDRCanvasToCubemapFace(Canvas *source, Texture *cubemap, int face) override
Copies hdr canvas to cubemap face.
bool setMeshSkinningData(Mesh *mesh, const uint16_t *joints4, const float *weights4, int vertexCount) override
Sets the mesh skinning data.
void drawPrimitiveCanvas(const PrimitiveCanvas2D &canvas) override
Draws primitive canvas.
bool reloadTextureFromFile(const std::string &filename) override
Reloads texture from file.
void drawMeshShadowAlpha(Mesh *mesh, const glm::mat4 &lightMVP, Texture *albedo=nullptr, bool doubleSided=true, float lodWeight=1.f, bool lodFadeReverse=false, bool lodDither=false) override
Draws mesh shadow alpha.
void setCloudShadows(float strength, float worldCell, float time, float windSpeed, float windAngle, float coverage, float detail) override
Sets the cloud shadows.
eve::Result< void > updateTextureRegion(Texture *texture, int x, int y, int width, int height, std::span< const std::uint8_t > rgba, std::size_t bytesPerRow=0) override
Updates texture region.
bool filterHDRReflectionCubemap(Texture *cubemap, int sampleCount=64) override
Filter hdr reflection cubemap.
void setTextureSampler(Texture *texture, const TextureSampler &sampler) override
Sets the texture sampler.
bool releaseMesh(Mesh *mesh) override
Release mesh.
void setMesh3DClip(float nearZ, float farZ) override
Sets the mesh 3 d clip.
void endShadowPass() override
Ends shadow pass.
void drawDecal(const glm::mat4 &model, Texture *albedo, Texture *normal, Texture *params, const float uvRect[4], float fade, float normalStrength, float roughnessStrength, float metalStrength, float emissiveStrength, int blendMode=0, int projectionMode=0, float blendSharpness=4.f, float parallaxScale=0.f, float parallaxMinLayers=8.f, float parallaxMaxLayers=24.f, float edgeFadeWidth=0.06f) override
Draws decal.
void setDecalCamera(const glm::mat4 &viewProj, float nearZ, float farZ) override
Sets the decal camera.
void setMesh3DSceneDepth(Texture *depth) override
Sets the mesh 3 d scene depth.
bool releaseTexture(Texture *texture) override
Release texture.
void drawTexturedRectShaderUV(Texture *texture, Shader *shader, float x, float y, float w, float h, float u0, float v0, float u1, float v1, const Color &color, bool rotatedUV=false, BlendMode blend=BlendMode::Alpha) override
Draws textured rect shader uv.
Shader * newShaderFromWgsl(const std::string &vertWgsl, const std::string &fragWgsl) override
Creates a shader from wgsl. @ownership Caller deletes unless documented otherwise.
Texture * getTexture() override
Returns the texture.
Canvas * newHDRCanvas(int width, int height) override
Creates a hdr canvas. @ownership Caller deletes unless documented otherwise.
void drawSolidRect(float x, float y, float w, float h, const Color &color, BlendMode blend=BlendMode::Alpha) override
Draws solid rect.
Canvas * newCanvas(int width, int height) override
Creates a canvas. @ownership Caller deletes unless documented otherwise.
void drawTexturedRectShader(Texture *texture, Shader *shader, float x, float y, float w, float h, const Color &color) override
Draws textured rect shader.
void drawTexturedRectShaderDepth(Texture *color, Texture *depth, Shader *shader, float x, float y, float w, float h, const Color &tint) override
Draws textured rect shader depth.
Texture * newHDRCubemap(int faceSize) override
Creates a hdr cubemap. @ownership Caller deletes unless documented otherwise.
Canvas * getCanvas() const override
Returns the canvas.
void setVSync(bool enabled) override
Sets the v sync.
bool updateTexture(Texture *texture, int width, int height, const uint8_t *rgba) override
Updates texture.
void setMesh3DSSAO(float intensity) override
Sets the mesh 3 dssao.
bool isCanvasActive() const override
True when canvas active.
void setMesh3DSurface(SurfaceMode mode, BlendMode blend, bool depthWrite, bool doubleSided, float alphaCutoff, const std::string &alphaTechnique="cutoff") override
Sets the mesh 3 d surface.
void beginShadowPass(int cascadeIndex) override
Begins shadow pass.
Texture * getSceneColorTexture() override
Returns the scene color texture.
void initHeadless(int width, int height) override
Initialize the renderer without a window or swapchain (headless mode). Creates a GPU device and offsc...
static DeviceDone requestDevice(AdapterDone &&prev)
Blocking device request (plus queue + capability capture). Throws eve::graphics::Exception on failure...
static InstanceDone createInstance()
Create a wgpu Instance (native requests the TimedWaitAny feature).
static AdapterDone requestAdapter(InstanceDone &&prev, const wgpu::Surface &compatibleSurface)
Blocking adapter request. Drives ProcessEvents until the callback fires; throws eve::graphics::Except...
Offscreen render target (RGBA8Unorm color, optional Depth32Float). 2D batches are flushed into the ca...
int getWidth() const override
Returns the width.
int getHeight() const override
Returns the height.
Texture * getTexture() override
Returns the texture.
bool isHDR() const
True when hdr.
Represents raw pixel data.
RAII keep-alive for one RuntimeObjectRegistry entry.
T * get() const noexcept
Borrows the pinned object; valid until the pin is released.
std::vector< ParamSpec > params
const char * kDecalFragWgsl
const char * kColorVertWgsl
const char * kMesh3DFragWgsl
const char * kDecalVertWgsl
const char * kColorFragWgsl
const char * kTexturedVertWgsl
WGPUStringView sv(const char *s)
Build a WGPUStringView from a C string (null-safe, length auto-computed).
const char * kMesh3DVertWgsl
Immutable shader source. @borrowed Static storage; valid for the process lifetime.
const char * kMesh3DGbufferFragWgsl
Immutable GBuffer fill WGSL. @borrowed Static storage; valid for the process lifetime.
const char * kMesh3DShadowAlphaVertWgsl
Immutable shader source. @borrowed Static storage; valid for the process lifetime.
const char * kLit2DVertWgsl
const char * kMesh3DShadowVertWgsl
Immutable shader source. @borrowed Static storage; valid for the process lifetime.
const char * kSSAOFragWgsl
const char * kTexturedFragWgsl
const char * kLit2DFragWgsl
wgpu::ShaderModule makeWgslModule(wgpu::Device &dev, const char *wgsl)
Create an owning shader module; retain it until pipeline creation completes.
const char * kMesh3DShadowAlphaFragWgsl
const char * kMesh3DClusteredVertWgsl
const char * kMesh3DGbufferAlphaFragWgsl
Immutable GBuffer alpha-cutout WGSL. @borrowed Static storage; valid for the process lifetime.
const char * kMesh3DClusteredFragWgsl
const char * kMesh3DGbufferVertWgsl
Immutable shader source. @borrowed Static storage; valid for the process lifetime.
void fillMeshAttributes(WGPUVertexAttribute(&attrs)[6])
std::vector< uint8_t > buildGgxCubemapMipChain(const uint8_t *rgbaFaces, uint32_t faceSize, uint32_t mipLevels, uint32_t sampleCount=32u)
Build a mip-major RGBA8 IBL chain: GGX specular mips plus final diffuse irradiance.
SurfaceMode
How a 3D surface contributes to depth and color passes.
ResolvedPrimitiveTriangles resolvePrimitiveStrokes2D(const PrimitiveCanvas2D &canvas, glm::ivec2 viewport)
Resolves 2D stroke segment bodies into clip-space triangles.
eve::Color Color
RGBA color used by every graphics draw call. Lives inside eve::graphics so including a graphics heade...
Light3DGpu ClusteredLightGpu
int mipmapCountForSize(int width, int height)
Full mip chain count for a 2D image (including base level).
void detachGraphicsArtifactProvider(Graphics *graphics) noexcept
Detach a Graphics instance before its derived backend is destroyed.
eve::BlendMode BlendMode
Compatibility alias for the shared 2D blend mode.
SkinInfluenceLimit
Number of strongest vertex influences retained by GPU skinning.
constexpr HexDirection next(HexDirection d) noexcept
The next direction clockwise (NW wraps to NE).
std::vector< int64_t > attrs(const Node &n, const char *key, std::vector< int64_t > fallback)
Attrs.
BlendMode
Render-neutral 2D blend mode shared by graphics-facing modules.
CPU-built clustered lighting upload for one frame/camera. Point lights are clustered; directional lig...
std::vector< ClusteredLightGpu > lights
std::vector< uint32_t > lightIndices
std::vector< ClusterTableEntry > clusterTable
Lighting2DUBO public API.
Lighting3DPack public API.
std::array< glm::vec4, kMaxDiffuseVolumeProbes > diffuseVolumePosition
std::array< glm::vec4, kMaxDiffuseVolumeProbes > diffuseVolumeExtent
bool diffuseVolumeTrilinearCell
std::array< glm::vec4, kMaxDiffuseVolumeProbes *9 > diffuseVolumeSh
static constexpr int kMaxLights
Light3DGpu lights[kMaxLights]
bool diffuseProbeShEnabled
int diffuseVolumeProbeCount
std::array< glm::vec4, 9 > diffuseProbeSh
Backend-owned layout facts for a mesh uploaded through Graphics.
std::uint32_t vertexCount
Resolved triangle vertex consumed by primitive GPU backends.
ReflectionProbeUpload public API.
static constexpr int kMaxProbes
One contiguous 2D blend/order batch in the resolved vertex stream.
static constexpr int kMapSize
static constexpr int kCascades
static constexpr int kTotalLayers
Options for Graphics::newTexture / newCubemap. When generateMipmaps is true and sampler....
One borrowed RGBA8 source rectangle for a batched texture update. @ownership rgba remains owned by th...
Sampler state for a Texture (filter, wrap, mip LOD, anisotropy). Defaults match historical engine beh...
static TextureSampler linear()
Linear.
static TextureSampler linearMipmap()
Trilinear (linear + linear mips). Caller should create the texture with generateMipmaps.
Vertex/index buffers for one mesh.
std::vector< MeshVertex > cpuVertices
A compiled shader: one WebGPU pipeline + layout. Also holds the WGSL sources so custom shaders can be...
Texture resources backed by a wgpu texture + view + sampler + bind groups.