15#include "common/config.h"
20#include <assimp/scene.h>
27#include <glm/gtc/constants.hpp>
30#include <SDL2/SDL_syswm.h>
36#if defined(__EMSCRIPTEN__)
37#include <emscripten/emscripten.h>
45WGPUStringView sv(
const char *
s) {
46 return WGPUStringView{
s,
s ? std::strlen(
s) : 0};
50bool copyTextureToCpu(wgpu::Instance &instance, wgpu::Device &
device, wgpu::Queue &queue,
51 wgpu::Texture src,
int width,
int height, std::vector<uint8_t> &outRgba);
54wgpu::Sampler createLinearSampler(wgpu::Device &dev) {
55 WGPUSamplerDescriptor
d{};
56 d.label = sv(
"eve_linear");
57 d.addressModeU = WGPUAddressMode_ClampToEdge;
58 d.addressModeV = WGPUAddressMode_ClampToEdge;
59 d.addressModeW = WGPUAddressMode_ClampToEdge;
60 d.magFilter = WGPUFilterMode_Linear;
61 d.minFilter = WGPUFilterMode_Linear;
62 d.mipmapFilter = WGPUMipmapFilterMode_Linear;
64 d.lodMaxClamp = 1000.f;
66 return dev.CreateSampler(
reinterpret_cast<const wgpu::SamplerDescriptor*
>(&
d));
71 while (
p <
v)
p <<= 1;
78 for (uint32_t i = 0; i < kFramesInFlight; ++i) {
79 uboArenas.emplace_back();
80 vertexArenas.emplace_back();
91 sdlWindow = nativeWindow;
92 if (deviceInitDone)
return;
94 createInstanceAndAdapter();
96 if (!device)
throw Exception(
"WebGPU: device request failed");
98 queue = device.GetQueue();
101 WGPUSurfaceDescriptor surfDesc{};
102 surfDesc.label = sv(
"eve_surface");
103#if defined(__EMSCRIPTEN__)
104 WGPUEmscriptenSurfaceSourceCanvasHTMLSelector canvasSel{};
105 canvasSel.chain.sType = WGPUSType_EmscriptenSurfaceSourceCanvasHTMLSelector;
106 canvasSel.selector = sv(
"#canvas");
107 surfDesc.nextInChain = &canvasSel.chain;
108 surface = instance.CreateSurface(
reinterpret_cast<const wgpu::SurfaceDescriptor*
>(&surfDesc));
109 surfaceFormat = WGPUTextureFormat_BGRA8Unorm;
111 SDL_SysWMinfo wminfo;
112 SDL_VERSION(&wminfo.version);
113 if (!SDL_GetWindowWMInfo(
static_cast<SDL_Window *
>(sdlWindow), &wminfo))
114 throw Exception(
"WebGPU: SDL_GetWindowWMInfo failed: %s", SDL_GetError());
116 WGPUSurfaceSourceWindowsHWND winChain{};
117 winChain.chain.sType = WGPUSType_SurfaceSourceWindowsHWND;
118 winChain.hwnd = wminfo.info.win.window;
119 winChain.hinstance = GetModuleHandle(
nullptr);
120 surfDesc.nextInChain = &winChain.chain;
121 surface = instance.CreateSurface(
reinterpret_cast<const wgpu::SurfaceDescriptor*
>(&surfDesc));
122#elif defined(__linux__)
123 if (wminfo.subsystem == SDL_SYSWM_X11) {
124 WGPUSurfaceSourceXlibWindow x11Chain{};
125 x11Chain.chain.sType = WGPUSType_SurfaceSourceXlibWindow;
126 x11Chain.display = wminfo.info.x11.display;
127 x11Chain.window = wminfo.info.x11.window;
128 surfDesc.nextInChain = &x11Chain.chain;
129 surface = instance.CreateSurface(
reinterpret_cast<const wgpu::SurfaceDescriptor*
>(&surfDesc));
130 }
else if (wminfo.subsystem == SDL_SYSWM_WAYLAND) {
131 WGPUSurfaceSourceWaylandSurface wlChain{};
132 wlChain.chain.sType = WGPUSType_SurfaceSourceWaylandSurface;
133 wlChain.display = wminfo.info.wl.display;
134 wlChain.surface = wminfo.info.wl.surface;
135 surfDesc.nextInChain = &wlChain.chain;
136 surface = instance.CreateSurface(
reinterpret_cast<const wgpu::SurfaceDescriptor*
>(&surfDesc));
138 throw Exception(
"WebGPU: unsupported SDL window subsystem on Linux");
140#elif defined(__APPLE__)
141 throw Exception(
"WebGPU: native macOS surface (Metal layer) not yet supported; "
142 "use the Emscripten browser build or the Vulkan backend on macOS");
144 throw Exception(
"WebGPU: unsupported native platform for surface creation");
146 if (!surface)
throw Exception(
"WebGPU: surface creation failed");
149 WGPUSurfaceCapabilities caps{};
150 if (wgpuSurfaceGetCapabilities(surface.Get(), adapter.Get(), &caps) == WGPUStatus_Success &&
151 caps.formatCount > 0) {
152 surfaceFormat = caps.formats[0];
154 wgpuSurfaceCapabilitiesFreeMembers(&caps);
157 swapchainConfigured =
false;
161 createPipelineResources();
162 createShadowResources();
163 createDefaultTextures();
167void Graphics::createInstanceAndAdapter() {
168 instance = wgpu::CreateInstance();
169 if (!instance)
throw Exception(
"WebGPU: wgpuCreateInstance failed");
171 WGPURequestAdapterOptions opts{};
172 opts.compatibleSurface = surface.Get();
173 opts.powerPreference = WGPUPowerPreference_HighPerformance;
175 adapterReceived =
false;
176 WGPURequestAdapterCallbackInfo cbInfo{};
177 cbInfo.nextInChain =
nullptr;
178 cbInfo.mode = WGPUCallbackMode_AllowProcessEvents;
179 cbInfo.callback = [](WGPURequestAdapterStatus
status, WGPUAdapter
a, WGPUStringView msg,
180 void *userdata1,
void * ) {
181 auto *self =
static_cast<Graphics *
>(userdata1);
182 if (
status == WGPURequestAdapterStatus_Success &&
a) {
183 self->adapter = wgpu::Adapter(
a);
186 msg.data ? std::string(msg.data, msg.length) :
"unknown adapter error";
188 self->adapterReceived.store(
true);
190 cbInfo.userdata1 =
this;
191 cbInfo.userdata2 =
nullptr;
193 wgpuInstanceRequestAdapter(instance.Get(), &opts, cbInfo);
196 throw Exception(
"WebGPU: no adapter found (%s)", adapterError.c_str());
200void Graphics::requestDevice() {
201 WGPUDeviceDescriptor devDesc{};
202 devDesc.label = sv(
"eve_device");
204 deviceReceived =
false;
205 WGPURequestDeviceCallbackInfo cbInfo{};
206 cbInfo.nextInChain =
nullptr;
207 cbInfo.mode = WGPUCallbackMode_AllowProcessEvents;
208 cbInfo.callback = [](WGPURequestDeviceStatus
status, WGPUDevice
d, WGPUStringView msg,
209 void *userdata1,
void * ) {
210 auto *self =
static_cast<Graphics *
>(userdata1);
211 if (
status == WGPURequestDeviceStatus_Success &&
d) {
212 self->device = wgpu::Device(
d);
214 self->deviceReceived.store(
true);
215 self->deviceError.clear();
218 msg.data ? std::string(msg.data, msg.length) :
"unknown device error";
219 self->deviceReceived.store(
true);
222 cbInfo.userdata1 =
this;
223 cbInfo.userdata2 =
nullptr;
225 wgpuAdapterRequestDevice(adapter.Get(), &devDesc, cbInfo);
228 throw Exception(
"WebGPU: device request failed (%s)", deviceError.c_str());
232void Graphics::waitForAdapter() {
233 while (!adapterReceived.load()) {
234#if defined(__EMSCRIPTEN__)
239 wgpuInstanceProcessEvents(instance.Get());
243void Graphics::waitForDevice() {
244 while (!deviceReceived.load()) {
245#if defined(__EMSCRIPTEN__)
248 wgpuInstanceProcessEvents(instance.Get());
255 markSwapchainDirty();
262 pixelH = pixelheight;
267void Graphics::configureSurface(
int width,
int height) {
268 if (!surface || !device ||
width <= 0 ||
height <= 0)
return;
269 WGPUSurfaceConfiguration cfg{};
270 cfg.nextInChain =
nullptr;
271 cfg.device = device.Get();
272 cfg.width =
static_cast<uint32_t
>(
width);
273 cfg.height =
static_cast<uint32_t
>(
height);
274 cfg.format = surfaceFormat;
275 cfg.usage = WGPUTextureUsage_RenderAttachment;
276 cfg.viewFormatCount = 0;
277 cfg.viewFormats =
nullptr;
279 cfg.presentMode = WGPUPresentMode_Fifo;
283 cfg.alphaMode = WGPUCompositeAlphaMode_Opaque;
284 surface.Configure(
reinterpret_cast<const wgpu::SurfaceConfiguration*
>(&cfg));
285 swapchainConfigured =
true;
287 int w = pixelW > 0 ? pixelW :
width;
288 int h = pixelH > 0 ? pixelH :
height;
289 if (
w > 0 &&
h > 0) {
290 createSceneColorResources(
w,
h);
291 createShadowResources();
295void Graphics::rebuildSwapchainIfNeeded() {
296 if (!swapchainConfigured && surface && logicalW > 0 && logicalH > 0) {
297 configureSurface(logicalW, logicalH);
305void Graphics::createDefaultTextures() {
306 uint8_t white[4] = {255, 255, 255, 255};
307 whiteTexture =
static_cast<GpuTexture *
>(
newTexture(1, 1, white,
false,
false)->
gpuHandle);
310 uint8_t flatNrm[4] = {128, 128, 255, 255};
311 flatNormalTexture =
static_cast<GpuTexture *
>(
newTexture(1, 1, flatNrm,
false,
false)->
gpuHandle);
312 flatNormalTexture3D = flatNormalTexture;
314 uint8_t flatH[4] = {0, 0, 0, 255};
315 flatHeightTexture3D =
static_cast<GpuTexture *
>(
newTexture(1, 1, flatH,
false,
false)->
gpuHandle);
320 auto *gpu =
new GpuTexture();
321 WGPUTextureDescriptor td{};
322 td.label = sv(
"eve_flat_depth");
323 td.dimension = WGPUTextureDimension_2D;
326 td.format = WGPUTextureFormat_Depth32Float;
327 td.mipLevelCount = 1;
328 td.usage = WGPUTextureUsage_TextureBinding;
329 gpu->texture = device.CreateTexture(
reinterpret_cast<const wgpu::TextureDescriptor*
>(&td));
330 WGPUTextureViewDescriptor vd{};
331 vd.format = WGPUTextureFormat_Depth32Float;
332 vd.dimension = WGPUTextureViewDimension_2D;
334 vd.mipLevelCount = 1;
335 vd.baseArrayLayer = 0;
336 vd.arrayLayerCount = 1;
337 gpu->view = gpu->texture.CreateView(
reinterpret_cast<const wgpu::TextureViewDescriptor*
>(&vd));
340 flatDepthTexture3D = gpu;
344 uint8_t cubeFace[4] = {255, 255, 255, 255};
345 uint8_t cubeData[24];
346 for (
int f = 0;
f < 6; ++
f) std::memcpy(cubeData +
f * 4, cubeFace, 4);
352 mainSampler = makeSampler(def, 1);
359void Graphics::createPipelineResources() {
361 createMesh3DPipelines();
362 createShadowPipelines();
363 createGbufferPipelines();
364 createVoxelPipelines();
371wgpu::BindGroupLayout Graphics::make2DBindGroupLayout() {
372 WGPUBindGroupLayoutEntry entries[5]{};
374 entries[0].binding = 0;
375 entries[0].visibility = WGPUShaderStage_Fragment;
376 entries[0].texture.sampleType = WGPUTextureSampleType_Float;
377 entries[0].texture.viewDimension = WGPUTextureViewDimension_2D;
378 entries[0].texture.multisampled =
false;
380 entries[1].binding = 1;
381 entries[1].visibility = WGPUShaderStage_Fragment;
382 entries[1].texture.sampleType = WGPUTextureSampleType_Float;
383 entries[1].texture.viewDimension = WGPUTextureViewDimension_2D;
385 entries[2].binding = 2;
386 entries[2].visibility = WGPUShaderStage_Fragment;
387 entries[2].sampler.type = WGPUSamplerBindingType_Filtering;
389 entries[3].binding = 3;
390 entries[3].visibility = WGPUShaderStage_Fragment;
391 entries[3].sampler.type = WGPUSamplerBindingType_Filtering;
394 entries[4].binding = 4;
395 entries[4].visibility = WGPUShaderStage_Vertex | WGPUShaderStage_Fragment;
396 entries[4].buffer.type = WGPUBufferBindingType_Uniform;
397 entries[4].buffer.hasDynamicOffset =
true;
398 entries[4].buffer.minBindingSize =
401 WGPUBindGroupLayoutDescriptor desc{};
402 desc.label = sv(
"eve_2d");
404 desc.entries = entries;
405 return device.CreateBindGroupLayout(
reinterpret_cast<const wgpu::BindGroupLayoutDescriptor*
>(&desc));
408wgpu::BindGroupLayout Graphics::makeMesh3DBindGroupLayout() {
409 WGPUBindGroupLayoutEntry entries[10]{};
411 entries[0].binding = 0;
412 entries[0].visibility = WGPUShaderStage_Vertex | WGPUShaderStage_Fragment;
413 entries[0].buffer.type = WGPUBufferBindingType_Uniform;
414 entries[0].buffer.hasDynamicOffset =
true;
415 entries[0].buffer.minBindingSize =
sizeof(Mesh3DUBO);
417 entries[1].binding = 1;
418 entries[1].visibility = WGPUShaderStage_Fragment;
419 entries[1].texture.sampleType = WGPUTextureSampleType_Float;
420 entries[1].texture.viewDimension = WGPUTextureViewDimension_2D;
422 entries[2].binding = 2;
423 entries[2].visibility = WGPUShaderStage_Fragment;
424 entries[2].texture.sampleType = WGPUTextureSampleType_Float;
425 entries[2].texture.viewDimension = WGPUTextureViewDimension_2D;
427 entries[3].binding = 3;
428 entries[3].visibility = WGPUShaderStage_Fragment;
429 entries[3].texture.sampleType = WGPUTextureSampleType_Float;
430 entries[3].texture.viewDimension = WGPUTextureViewDimension_Cube;
432 entries[4].binding = 4;
433 entries[4].visibility = WGPUShaderStage_Fragment;
434 entries[4].buffer.type = WGPUBufferBindingType_Uniform;
435 entries[4].buffer.hasDynamicOffset =
true;
436 entries[4].buffer.minBindingSize =
sizeof(ShadowUBO);
438 entries[5].binding = 5;
439 entries[5].visibility = WGPUShaderStage_Fragment;
440 entries[5].texture.sampleType = WGPUTextureSampleType_Depth;
441 entries[5].texture.viewDimension = WGPUTextureViewDimension_2DArray;
443 entries[6].binding = 6;
444 entries[6].visibility = WGPUShaderStage_Fragment;
445 entries[6].texture.sampleType = WGPUTextureSampleType_Float;
446 entries[6].texture.viewDimension = WGPUTextureViewDimension_2D;
448 entries[7].binding = 7;
449 entries[7].visibility = WGPUShaderStage_Fragment;
450 entries[7].sampler.type = WGPUSamplerBindingType_Filtering;
452 entries[8].binding = 8;
453 entries[8].visibility = WGPUShaderStage_Fragment;
454 entries[8].sampler.type = WGPUSamplerBindingType_Comparison;
457 entries[9].binding = 9;
458 entries[9].visibility = WGPUShaderStage_Fragment;
459 entries[9].texture.sampleType = WGPUTextureSampleType_Depth;
460 entries[9].texture.viewDimension = WGPUTextureViewDimension_2D;
462 WGPUBindGroupLayoutDescriptor desc{};
463 desc.label = sv(
"eve_mesh3d");
464 desc.entryCount = 10;
465 desc.entries = entries;
466 return device.CreateBindGroupLayout(
reinterpret_cast<const wgpu::BindGroupLayoutDescriptor*
>(&desc));
469wgpu::BindGroupLayout Graphics::makeShadowBindGroupLayout() {
470 WGPUBindGroupLayoutEntry entries[1]{};
471 entries[0].binding = 0;
472 entries[0].visibility = WGPUShaderStage_Vertex;
473 entries[0].buffer.type = WGPUBufferBindingType_Uniform;
474 entries[0].buffer.hasDynamicOffset =
true;
475 entries[0].buffer.minBindingSize = 64;
477 WGPUBindGroupLayoutDescriptor desc{};
478 desc.label = sv(
"eve_shadow");
480 desc.entries = entries;
481 return device.CreateBindGroupLayout(
reinterpret_cast<const wgpu::BindGroupLayoutDescriptor*
>(&desc));
484wgpu::BindGroupLayout Graphics::makeGbufferBindGroupLayout() {
485 WGPUBindGroupLayoutEntry entries[3]{};
487 entries[0].binding = 0;
488 entries[0].visibility = WGPUShaderStage_Vertex | WGPUShaderStage_Fragment;
489 entries[0].buffer.type = WGPUBufferBindingType_Uniform;
490 entries[0].buffer.hasDynamicOffset =
true;
491 entries[0].buffer.minBindingSize = 128;
493 entries[1].binding = 1;
494 entries[1].visibility = WGPUShaderStage_Fragment;
495 entries[1].texture.sampleType = WGPUTextureSampleType_Float;
496 entries[1].texture.viewDimension = WGPUTextureViewDimension_2D;
498 entries[2].binding = 2;
499 entries[2].visibility = WGPUShaderStage_Fragment;
500 entries[2].sampler.type = WGPUSamplerBindingType_Filtering;
502 WGPUBindGroupLayoutDescriptor desc{};
503 desc.label = sv(
"eve_gbuffer");
505 desc.entries = entries;
506 return device.CreateBindGroupLayout(
reinterpret_cast<const wgpu::BindGroupLayoutDescriptor*
>(&desc));
509wgpu::BindGroupLayout Graphics::makeVoxelBindGroupLayout() {
510 WGPUBindGroupLayoutEntry entries[3]{};
512 entries[0].binding = 0;
513 entries[0].visibility = WGPUShaderStage_Vertex;
514 entries[0].buffer.type = WGPUBufferBindingType_Uniform;
515 entries[0].buffer.hasDynamicOffset =
true;
516 entries[0].buffer.minBindingSize = 112;
518 entries[1].binding = 1;
519 entries[1].visibility = WGPUShaderStage_Fragment;
520 entries[1].texture.sampleType = WGPUTextureSampleType_Float;
521 entries[1].texture.viewDimension = WGPUTextureViewDimension_2D;
523 entries[2].binding = 2;
524 entries[2].visibility = WGPUShaderStage_Fragment;
525 entries[2].sampler.type = WGPUSamplerBindingType_Filtering;
527 WGPUBindGroupLayoutDescriptor desc{};
528 desc.label = sv(
"eve_voxel");
530 desc.entries = entries;
531 return device.CreateBindGroupLayout(
reinterpret_cast<const wgpu::BindGroupLayoutDescriptor*
>(&desc));
534wgpu::PipelineLayout Graphics::make2DPipelineLayout() {
535 WGPUBindGroupLayout bgl = tex2DSetLayout.Get();
536 WGPUPipelineLayoutDescriptor
d{};
537 d.label = sv(
"eve_2d_layout");
538 d.bindGroupLayoutCount = 1;
539 d.bindGroupLayouts = &bgl;
540 return device.CreatePipelineLayout(
reinterpret_cast<const wgpu::PipelineLayoutDescriptor*
>(&
d));
543wgpu::PipelineLayout Graphics::makeMesh3DPipelineLayout() {
544 WGPUBindGroupLayout bgl = mesh3dSetLayout.Get();
545 WGPUPipelineLayoutDescriptor
d{};
546 d.label = sv(
"eve_mesh3d_layout");
547 d.bindGroupLayoutCount = 1;
548 d.bindGroupLayouts = &bgl;
549 return device.CreatePipelineLayout(
reinterpret_cast<const wgpu::PipelineLayoutDescriptor*
>(&
d));
552wgpu::PipelineLayout Graphics::makeShadowPipelineLayout() {
553 WGPUBindGroupLayout bgl = shadowSetLayout.Get();
554 WGPUPipelineLayoutDescriptor
d{};
555 d.label = sv(
"eve_shadow_layout");
556 d.bindGroupLayoutCount = 1;
557 d.bindGroupLayouts = &bgl;
558 return device.CreatePipelineLayout(
reinterpret_cast<const wgpu::PipelineLayoutDescriptor*
>(&
d));
561wgpu::PipelineLayout Graphics::makeGbufferPipelineLayout() {
562 WGPUBindGroupLayout bgl = gbufferSetLayout.Get();
563 WGPUPipelineLayoutDescriptor
d{};
564 d.label = sv(
"eve_gbuffer_layout");
565 d.bindGroupLayoutCount = 1;
566 d.bindGroupLayouts = &bgl;
567 return device.CreatePipelineLayout(
reinterpret_cast<const wgpu::PipelineLayoutDescriptor*
>(&
d));
570wgpu::PipelineLayout Graphics::makeVoxelPipelineLayout() {
571 WGPUBindGroupLayout bgl = voxelSetLayout.Get();
572 WGPUPipelineLayoutDescriptor
d{};
573 d.label = sv(
"eve_voxel_layout");
574 d.bindGroupLayoutCount = 1;
575 d.bindGroupLayouts = &bgl;
576 return device.CreatePipelineLayout(
reinterpret_cast<const wgpu::PipelineLayoutDescriptor*
>(&
d));
589wgpu::ShaderModule makeWgslModule(wgpu::Device &dev,
const char *wgsl) {
590 WGPUShaderSourceWGSL wgslDesc{};
591 wgslDesc.chain.sType = WGPUSType_ShaderSourceWGSL;
592 wgslDesc.code = sv(wgsl);
593 WGPUShaderModuleDescriptor desc{};
594 desc.nextInChain = &wgslDesc.chain;
595 return dev.CreateShaderModule(
reinterpret_cast<const wgpu::ShaderModuleDescriptor*
>(&desc));
601WGPUShaderModuleDescriptor mdDesc(
const std::string &code) {
602 static WGPUShaderSourceWGSL wd{};
603 wd.chain.sType = WGPUSType_ShaderSourceWGSL;
604 wd.code = sv(code.c_str());
605 WGPUShaderModuleDescriptor md{};
606 md.nextInChain = &wd.chain;
610void fillVertexLayout(WGPUVertexBufferLayout &
layout, uint64_t stride,
611 const WGPUVertexAttribute *attrs, uint32_t attrCount) {
612 layout.arrayStride = stride;
613 layout.stepMode = WGPUVertexStepMode_Vertex;
614 layout.attributeCount = attrCount;
615 layout.attributes = attrs;
618WGPUBlendState alphaBlend() {
620 b.color.srcFactor = WGPUBlendFactor_SrcAlpha;
621 b.color.dstFactor = WGPUBlendFactor_OneMinusSrcAlpha;
622 b.color.operation = WGPUBlendOperation_Add;
623 b.alpha.srcFactor = WGPUBlendFactor_One;
624 b.alpha.dstFactor = WGPUBlendFactor_OneMinusSrcAlpha;
625 b.alpha.operation = WGPUBlendOperation_Add;
629WGPUBlendState noBlend() {
631 b.color.srcFactor = WGPUBlendFactor_One;
632 b.color.dstFactor = WGPUBlendFactor_Zero;
633 b.color.operation = WGPUBlendOperation_Add;
634 b.alpha.srcFactor = WGPUBlendFactor_One;
635 b.alpha.dstFactor = WGPUBlendFactor_Zero;
636 b.alpha.operation = WGPUBlendOperation_Add;
640WGPUBlendState additiveBlend() {
642 b.color.srcFactor = WGPUBlendFactor_SrcAlpha;
643 b.color.dstFactor = WGPUBlendFactor_One;
644 b.color.operation = WGPUBlendOperation_Add;
645 b.alpha.srcFactor = WGPUBlendFactor_One;
646 b.alpha.dstFactor = WGPUBlendFactor_One;
647 b.alpha.operation = WGPUBlendOperation_Add;
655wgpu::RenderPipeline make2DColorPipeline(wgpu::Device &dev, WGPUTextureFormat format,
657 WGPUVertexAttribute attrs[2] = {};
658 attrs[0].format = WGPUVertexFormat_Float32x2;
660 attrs[0].shaderLocation = 0;
661 attrs[1].format = WGPUVertexFormat_Float32x4;
663 attrs[1].shaderLocation = 1;
664 WGPUVertexBufferLayout vb{};
665 fillVertexLayout(vb, 24, attrs, 2);
667 WGPUColorTargetState target{};
668 target.format = format;
669 target.blend =
nullptr;
674 target.writeMask = WGPUColorWriteMask_All;
675 WGPUBlendState bs = alphaBlend();
677 bs = additiveBlend();
686 WGPUPipelineLayoutDescriptor pld{};
687 pld.label = sv(
"eve_2d_color_layout");
688 pld.bindGroupLayoutCount = 0;
689 pld.bindGroupLayouts =
nullptr;
690 wgpu::PipelineLayout emptyLayout = dev.CreatePipelineLayout(
reinterpret_cast<const wgpu::PipelineLayoutDescriptor*
>(&pld));
692 WGPURenderPipelineDescriptor pd{};
693 pd.label = sv(
"eve_color2d");
697 wgpu::ShaderModule vertModule = makeWgslModule(dev,
kColorVertWgsl);
698 wgpu::ShaderModule fragModule = makeWgslModule(dev,
kColorFragWgsl);
699 pd.vertex.module = vertModule.Get();
700 pd.vertex.entryPoint = sv(
"vs_main");
701 pd.vertex.bufferCount = 1;
702 pd.vertex.buffers = &vb;
703 WGPUFragmentState fs{};
704 fs.module = fragModule.Get();
705 fs.entryPoint = sv(
"fs_main");
707 fs.targets = ⌖
709 pd.primitive.topology = WGPUPrimitiveTopology_TriangleList;
710 pd.primitive.frontFace = WGPUFrontFace_CCW;
711 pd.primitive.cullMode = WGPUCullMode_None;
712 pd.primitive.stripIndexFormat = WGPUIndexFormat_Undefined;
713 pd.depthStencil =
nullptr;
714 pd.multisample.count = 1;
717 pd.multisample.mask = 0xFFFFFFFFu;
718 return dev.CreateRenderPipeline(
reinterpret_cast<const wgpu::RenderPipelineDescriptor*
>(&pd));
721wgpu::RenderPipeline make2DTexturedPipeline(wgpu::Device &dev, wgpu::PipelineLayout
layout,
722 WGPUTextureFormat format,
BlendMode mode) {
723 WGPUVertexAttribute attrs[3] = {};
724 attrs[0].format = WGPUVertexFormat_Float32x2;
726 attrs[0].shaderLocation = 0;
727 attrs[1].format = WGPUVertexFormat_Float32x4;
729 attrs[1].shaderLocation = 1;
730 attrs[2].format = WGPUVertexFormat_Float32x2;
731 attrs[2].offset = 24;
732 attrs[2].shaderLocation = 2;
733 WGPUVertexBufferLayout vb{};
734 fillVertexLayout(vb, 32, attrs, 3);
736 WGPUColorTargetState target{};
737 target.format = format;
738 target.writeMask = WGPUColorWriteMask_All;
739 WGPUBlendState bs = alphaBlend();
741 bs = additiveBlend();
746 WGPURenderPipelineDescriptor pd{};
747 pd.label = sv(
"eve_textured2d");
751 pd.vertex.module = vertModule.Get();
752 pd.vertex.entryPoint = sv(
"vs_main");
753 pd.vertex.bufferCount = 1;
754 pd.vertex.buffers = &vb;
755 WGPUFragmentState fs{};
756 fs.module = fragModule.Get();
757 fs.entryPoint = sv(
"fs_main");
759 fs.targets = ⌖
761 pd.primitive.topology = WGPUPrimitiveTopology_TriangleList;
762 pd.primitive.frontFace = WGPUFrontFace_CCW;
763 pd.primitive.cullMode = WGPUCullMode_None;
764 pd.primitive.stripIndexFormat = WGPUIndexFormat_Undefined;
765 pd.depthStencil =
nullptr;
766 pd.multisample.count = 1;
769 pd.multisample.mask = 0xFFFFFFFFu;
770 return dev.CreateRenderPipeline(
reinterpret_cast<const wgpu::RenderPipelineDescriptor*
>(&pd));
773wgpu::RenderPipeline make2DLitPipeline(wgpu::Device &dev, wgpu::PipelineLayout
layout,
774 WGPUTextureFormat format,
bool blend) {
775 WGPUVertexAttribute attrs[3] = {};
776 attrs[0].format = WGPUVertexFormat_Float32x2;
778 attrs[0].shaderLocation = 0;
779 attrs[1].format = WGPUVertexFormat_Float32x4;
781 attrs[1].shaderLocation = 1;
782 attrs[2].format = WGPUVertexFormat_Float32x2;
783 attrs[2].offset = 24;
784 attrs[2].shaderLocation = 2;
785 WGPUVertexBufferLayout vb{};
786 fillVertexLayout(vb, 32, attrs, 3);
788 WGPUColorTargetState target{};
789 target.format = format;
790 target.writeMask = WGPUColorWriteMask_All;
791 WGPUBlendState bs = alphaBlend();
792 if (blend) target.blend = &bs;
794 WGPURenderPipelineDescriptor pd{};
795 pd.label = sv(
"eve_lit2d");
798 wgpu::ShaderModule fragModule = makeWgslModule(dev,
kLit2DFragWgsl);
799 pd.vertex.module = vertModule.Get();
800 pd.vertex.entryPoint = sv(
"vs_main");
801 pd.vertex.bufferCount = 1;
802 pd.vertex.buffers = &vb;
803 WGPUFragmentState fs{};
804 fs.module = fragModule.Get();
805 fs.entryPoint = sv(
"fs_main");
807 fs.targets = ⌖
809 pd.primitive.topology = WGPUPrimitiveTopology_TriangleList;
810 pd.primitive.frontFace = WGPUFrontFace_CCW;
811 pd.primitive.cullMode = WGPUCullMode_None;
812 pd.primitive.stripIndexFormat = WGPUIndexFormat_Undefined;
813 pd.depthStencil =
nullptr;
814 pd.multisample.count = 1;
817 pd.multisample.mask = 0xFFFFFFFFu;
818 return dev.CreateRenderPipeline(
reinterpret_cast<const wgpu::RenderPipelineDescriptor*
>(&pd));
823void Graphics::create2DPipelines() {
824 tex2DSetLayout = make2DBindGroupLayout();
825 tex2DPipelineLayout = make2DPipelineLayout();
827 colorPipeline = make2DColorPipeline(device, surfaceFormat,
BlendMode::Alpha);
828 texturedPipeline = make2DTexturedPipeline(device, tex2DPipelineLayout, surfaceFormat,
831 texturedAdditivePipeline = make2DTexturedPipeline(device, tex2DPipelineLayout, surfaceFormat,
833 colorOpaquePipeline = make2DColorPipeline(device, surfaceFormat,
BlendMode::Opaque);
834 texturedOpaquePipeline = make2DTexturedPipeline(device, tex2DPipelineLayout, surfaceFormat,
836 lit2dPipeline = make2DLitPipeline(device, tex2DPipelineLayout, surfaceFormat,
true);
838 offscreenColorPipeline = make2DColorPipeline(device, WGPUTextureFormat_RGBA8Unorm,
840 offscreenTexturedPipeline = make2DTexturedPipeline(device, tex2DPipelineLayout,
841 WGPUTextureFormat_RGBA8Unorm,
843 offscreenColorAdditivePipeline = make2DColorPipeline(device, WGPUTextureFormat_RGBA8Unorm,
845 offscreenTexturedAdditivePipeline =
846 make2DTexturedPipeline(device, tex2DPipelineLayout, WGPUTextureFormat_RGBA8Unorm,
848 offscreenColorOpaquePipeline = make2DColorPipeline(device, WGPUTextureFormat_RGBA8Unorm,
850 offscreenTexturedOpaquePipeline =
851 make2DTexturedPipeline(device, tex2DPipelineLayout, WGPUTextureFormat_RGBA8Unorm,
853 offscreenLitPipeline = make2DLitPipeline(device, tex2DPipelineLayout,
854 WGPUTextureFormat_RGBA8Unorm,
true);
857void Graphics::createMesh3DPipelines() {
858 mesh3dSetLayout = makeMesh3DBindGroupLayout();
859 mesh3dPipelineLayout = makeMesh3DPipelineLayout();
861 WGPUVertexAttribute attrs[3] = {};
862 attrs[0].format = WGPUVertexFormat_Float32x3;
864 attrs[0].shaderLocation = 0;
865 attrs[1].format = WGPUVertexFormat_Float32x3;
866 attrs[1].offset = 12;
867 attrs[1].shaderLocation = 1;
868 attrs[2].format = WGPUVertexFormat_Float32x2;
869 attrs[2].offset = 24;
870 attrs[2].shaderLocation = 2;
871 WGPUVertexBufferLayout vb{};
872 fillVertexLayout(vb, 32, attrs, 3);
874 WGPUDepthStencilState ds{};
875 ds.format = WGPUTextureFormat_Depth32Float;
876 ds.depthWriteEnabled = WGPUOptionalBool_True;
877 ds.depthCompare = WGPUCompareFunction_Less;
878 ds.stencilReadMask = 0;
879 ds.stencilWriteMask = 0;
881 WGPUColorTargetState target{};
882 target.format = sceneColorFormat;
883 target.blend =
nullptr;
884 target.writeMask = WGPUColorWriteMask_All;
886 WGPURenderPipelineDescriptor pd{};
887 pd.label = sv(
"eve_mesh3d");
888 pd.layout = mesh3dPipelineLayout.Get();
889 wgpu::ShaderModule vertModule = makeWgslModule(device,
kMesh3DVertWgsl);
890 wgpu::ShaderModule fragModule = makeWgslModule(device,
kMesh3DFragWgsl);
891 pd.vertex.module = vertModule.Get();
892 pd.vertex.entryPoint = sv(
"vs_main");
893 pd.vertex.bufferCount = 1;
894 pd.vertex.buffers = &vb;
895 WGPUFragmentState fs{};
896 fs.module = fragModule.Get();
897 fs.entryPoint = sv(
"fs_main");
899 fs.targets = ⌖
901 pd.primitive.topology = WGPUPrimitiveTopology_TriangleList;
902 pd.primitive.frontFace = WGPUFrontFace_CCW;
903 pd.primitive.cullMode = WGPUCullMode_None;
904 pd.primitive.stripIndexFormat = WGPUIndexFormat_Undefined;
905 pd.depthStencil = &ds;
906 pd.multisample.count = 1;
909 pd.multisample.mask = 0xFFFFFFFFu;
910 mesh3dPipeline = device.CreateRenderPipeline(
reinterpret_cast<const wgpu::RenderPipelineDescriptor*
>(&pd));
913void Graphics::createShadowPipelines() {
914 shadowSetLayout = makeShadowBindGroupLayout();
915 shadowPipelineLayout = makeShadowPipelineLayout();
917 WGPUVertexAttribute attrs[3] = {};
918 attrs[0].format = WGPUVertexFormat_Float32x3;
920 attrs[0].shaderLocation = 0;
921 attrs[1].format = WGPUVertexFormat_Float32x3;
922 attrs[1].offset = 12;
923 attrs[1].shaderLocation = 1;
924 attrs[2].format = WGPUVertexFormat_Float32x2;
925 attrs[2].offset = 24;
926 attrs[2].shaderLocation = 2;
927 WGPUVertexBufferLayout vb{};
928 fillVertexLayout(vb, 32, attrs, 3);
930 WGPUDepthStencilState ds{};
931 ds.format = WGPUTextureFormat_Depth32Float;
932 ds.depthWriteEnabled = WGPUOptionalBool_True;
933 ds.depthCompare = WGPUCompareFunction_Less;
934 ds.stencilReadMask = 0;
935 ds.stencilWriteMask = 0;
937 WGPURenderPipelineDescriptor pd{};
938 pd.label = sv(
"eve_shadow");
939 pd.layout = shadowPipelineLayout.Get();
941 pd.vertex.module = vertModule.Get();
942 pd.vertex.entryPoint = sv(
"vs_main");
943 pd.vertex.bufferCount = 1;
944 pd.vertex.buffers = &vb;
945 pd.fragment =
nullptr;
946 pd.primitive.topology = WGPUPrimitiveTopology_TriangleList;
947 pd.primitive.frontFace = WGPUFrontFace_CCW;
948 pd.primitive.cullMode = WGPUCullMode_None;
949 pd.primitive.stripIndexFormat = WGPUIndexFormat_Undefined;
950 pd.depthStencil = &ds;
951 pd.multisample.count = 1;
954 pd.multisample.mask = 0xFFFFFFFFu;
955 mesh3dShadowPipeline = device.CreateRenderPipeline(
reinterpret_cast<const wgpu::RenderPipelineDescriptor*
>(&pd));
958void Graphics::createGbufferPipelines() {
959 gbufferSetLayout = makeGbufferBindGroupLayout();
960 gbufferPipelineLayout = makeGbufferPipelineLayout();
962 WGPUVertexAttribute attrs[3] = {};
963 attrs[0].format = WGPUVertexFormat_Float32x3;
965 attrs[0].shaderLocation = 0;
966 attrs[1].format = WGPUVertexFormat_Float32x3;
967 attrs[1].offset = 12;
968 attrs[1].shaderLocation = 1;
969 attrs[2].format = WGPUVertexFormat_Float32x2;
970 attrs[2].offset = 24;
971 attrs[2].shaderLocation = 2;
972 WGPUVertexBufferLayout vb{};
973 fillVertexLayout(vb, 32, attrs, 3);
975 WGPUDepthStencilState ds{};
976 ds.format = WGPUTextureFormat_Depth32Float;
977 ds.depthWriteEnabled = WGPUOptionalBool_True;
978 ds.depthCompare = WGPUCompareFunction_Less;
979 ds.stencilReadMask = 0;
980 ds.stencilWriteMask = 0;
982 WGPUColorTargetState targets[3] = {};
983 for (
int i = 0; i < 3; ++i) {
984 targets[i].format = WGPUTextureFormat_RGBA8Unorm;
985 targets[i].blend =
nullptr;
986 targets[i].writeMask = WGPUColorWriteMask_All;
989 WGPURenderPipelineDescriptor pd{};
990 pd.label = sv(
"eve_gbuffer");
991 pd.layout = gbufferPipelineLayout.Get();
994 pd.vertex.module = vertModule.Get();
995 pd.vertex.entryPoint = sv(
"vs_main");
996 pd.vertex.bufferCount = 1;
997 pd.vertex.buffers = &vb;
998 WGPUFragmentState fs{};
999 fs.module = fragModule.Get();
1000 fs.entryPoint = sv(
"fs_main");
1002 fs.targets = targets;
1004 pd.primitive.topology = WGPUPrimitiveTopology_TriangleList;
1005 pd.primitive.frontFace = WGPUFrontFace_CCW;
1006 pd.primitive.cullMode = WGPUCullMode_None;
1007 pd.primitive.stripIndexFormat = WGPUIndexFormat_Undefined;
1008 pd.depthStencil = &ds;
1009 pd.multisample.count = 1;
1012 pd.multisample.mask = 0xFFFFFFFFu;
1013 mesh3dGbufferPipeline = device.CreateRenderPipeline(
reinterpret_cast<const wgpu::RenderPipelineDescriptor*
>(&pd));
1016void Graphics::createVoxelPipelines() {
1017 voxelSetLayout = makeVoxelBindGroupLayout();
1018 voxelPipelineLayout = makeVoxelPipelineLayout();
1020 WGPUVertexAttribute attrs[2] = {};
1021 attrs[0].format = WGPUVertexFormat_Float32x2;
1022 attrs[0].offset = 0;
1023 attrs[0].shaderLocation = 0;
1024 attrs[1].format = WGPUVertexFormat_Uint32;
1025 attrs[1].offset = 8;
1026 attrs[1].shaderLocation = 1;
1027 WGPUVertexBufferLayout cornerVb{};
1028 fillVertexLayout(cornerVb, 12, attrs, 2);
1029 WGPUVertexBufferLayout instanceVb{};
1030 instanceVb.arrayStride = 4;
1031 instanceVb.stepMode = WGPUVertexStepMode_Instance;
1032 instanceVb.attributeCount = 0;
1033 instanceVb.attributes =
nullptr;
1034 WGPUVertexBufferLayout vbs[2] = {cornerVb, instanceVb};
1036 WGPUDepthStencilState ds{};
1037 ds.format = WGPUTextureFormat_Depth32Float;
1038 ds.depthWriteEnabled = WGPUOptionalBool_True;
1039 ds.depthCompare = WGPUCompareFunction_Less;
1040 ds.stencilReadMask = 0;
1041 ds.stencilWriteMask = 0;
1043 WGPUColorTargetState target{};
1044 target.format = sceneColorFormat;
1045 target.blend =
nullptr;
1046 target.writeMask = WGPUColorWriteMask_All;
1048 WGPURenderPipelineDescriptor pd{};
1049 pd.label = sv(
"eve_voxel");
1050 pd.layout = voxelPipelineLayout.Get();
1053 pd.vertex.module = vertModule.Get();
1054 pd.vertex.entryPoint = sv(
"vs_main");
1055 pd.vertex.bufferCount = 2;
1056 pd.vertex.buffers = vbs;
1057 WGPUFragmentState fs{};
1058 fs.module = fragModule.Get();
1059 fs.entryPoint = sv(
"fs_main");
1061 fs.targets = ⌖
1063 pd.primitive.topology = WGPUPrimitiveTopology_TriangleList;
1064 pd.primitive.frontFace = WGPUFrontFace_CCW;
1065 pd.primitive.cullMode = WGPUCullMode_None;
1066 pd.primitive.stripIndexFormat = WGPUIndexFormat_Undefined;
1067 pd.depthStencil = &ds;
1068 pd.multisample.count = 1;
1071 pd.multisample.mask = 0xFFFFFFFFu;
1072 voxelRectPipeline = device.CreateRenderPipeline(
reinterpret_cast<const wgpu::RenderPipelineDescriptor*
>(&pd));
1075 float quad[8] = {0, 0, 1, 0, 1, 1, 0, 1};
1076 WGPUBufferDescriptor bd{};
1077 bd.label = sv(
"eve_voxel_quad");
1078 bd.size =
sizeof(quad);
1079 bd.usage = WGPUBufferUsage_CopyDst | WGPUBufferUsage_Vertex;
1080 bd.mappedAtCreation =
false;
1081 voxelUnitQuadVerts = device.CreateBuffer(
reinterpret_cast<const wgpu::BufferDescriptor*
>(&bd));
1082 queue.WriteBuffer(voxelUnitQuadVerts, 0, quad,
sizeof(quad));
1084 uint32_t indices[6] = {0, 1, 2, 2, 3, 0};
1085 WGPUBufferDescriptor ibd{};
1086 ibd.label = sv(
"eve_voxel_quad_idx");
1087 ibd.size =
sizeof(indices);
1088 ibd.usage = WGPUBufferUsage_CopyDst | WGPUBufferUsage_Index;
1089 ibd.mappedAtCreation =
false;
1090 voxelUnitQuadIndices = device.CreateBuffer(
reinterpret_cast<const wgpu::BufferDescriptor*
>(&ibd));
1091 queue.WriteBuffer(voxelUnitQuadIndices, 0, indices,
sizeof(indices));
1098uint32_t Graphics::UboArena::alloc(uint64_t size, uint64_t alignment) {
1099 uint64_t aligned = (used + alignment - 1) / alignment * alignment;
1100 used = aligned + size;
1101 return static_cast<uint32_t
>(aligned);
1104uint64_t Graphics::VertexArena::alloc(uint64_t bytes) {
1110Graphics::UboArena &Graphics::currentUboArena() {
return uboArenas[currentFrameSlot()]; }
1111Graphics::VertexArena &Graphics::currentVertexArena() {
return vertexArenas[currentFrameSlot()]; }
1113void Graphics::ensureUboArena(UboArena &arena, uint64_t bytes) {
1114 if (arena.buffer && arena.capacity >= bytes)
return;
1115 uint64_t size = arena.capacity;
1116 while (size < bytes) size = size ? size * 2 : (64u << 10);
1117 WGPUBufferDescriptor bd{};
1118 bd.label = sv(
"eve_ubo_arena");
1120 bd.usage = WGPUBufferUsage_CopyDst | WGPUBufferUsage_Uniform;
1121 bd.mappedAtCreation =
false;
1122 arena.buffer = device.CreateBuffer(
reinterpret_cast<const wgpu::BufferDescriptor*
>(&bd));
1123 arena.capacity = size;
1127void Graphics::ensureVertexArena(VertexArena &arena, uint64_t bytes) {
1128 if (arena.buffer && arena.capacity >= bytes)
return;
1129 uint64_t size = arena.capacity;
1130 while (size < bytes) size = size ? size * 2 : (256u << 10);
1131 WGPUBufferDescriptor bd{};
1132 bd.label = sv(
"eve_vertex_arena");
1134 bd.usage = WGPUBufferUsage_CopyDst | WGPUBufferUsage_Vertex;
1135 bd.mappedAtCreation =
false;
1136 arena.buffer = device.CreateBuffer(
reinterpret_cast<const wgpu::BufferDescriptor*
>(&bd));
1137 arena.capacity = size;
1145wgpu::Sampler Graphics::makeSampler(
const TextureSampler &
s, uint32_t mipLevels)
const {
1146 WGPUSamplerDescriptor
d{};
1147 d.label = sv(
"eve_sampler");
1148 d.addressModeU =
s.repeatU ? WGPUAddressMode_Repeat : WGPUAddressMode_ClampToEdge;
1149 d.addressModeV =
s.repeatV ? WGPUAddressMode_Repeat : WGPUAddressMode_ClampToEdge;
1150 d.addressModeW =
s.repeatW ? WGPUAddressMode_Repeat : WGPUAddressMode_ClampToEdge;
1154 d.mipmapFilter = WGPUMipmapFilterMode_Nearest;
1155 d.lodMinClamp = 0.f;
1156 d.lodMaxClamp = 0.f;
1160 d.lodMinClamp =
s.minLod;
1161 d.lodMaxClamp = std::min(std::max(
s.maxLod, 0.f),
float(mipLevels));
1163 float aniso =
s.maxAnisotropy > 1.f ?
s.maxAnisotropy : 1.f;
1164 d.maxAnisotropy = std::min(std::max(aniso, 1.f), std::max(maxSamplerAnisotropy, 1.f));
1165 return device.CreateSampler(
reinterpret_cast<const wgpu::SamplerDescriptor*
>(&
d));
1184 static_cast<const uint8_t *
>(
data->getData()), info);
1189 if (
data->getFormat() !=
"RGBA8")
1190 throw Exception(
"newTexture: only RGBA8 supported");
1192 static_cast<const uint8_t *
>(
data->getData()), info);
1199 auto gpu = std::make_unique<GpuTexture>();
1202 gpu->samplerState = info.
sampler;
1205 WGPUTextureDescriptor td{};
1206 td.label = sv(
"eve_tex2d");
1207 td.dimension = WGPUTextureDimension_2D;
1208 td.size.width =
static_cast<uint32_t
>(
width);
1209 td.size.height =
static_cast<uint32_t
>(
height);
1210 td.size.depthOrArrayLayers = 1;
1212 td.format = WGPUTextureFormat_RGBA8Unorm;
1213 td.mipLevelCount = gpu->mipLevels;
1214 td.usage = WGPUTextureUsage_TextureBinding | WGPUTextureUsage_CopyDst |
1215 WGPUTextureUsage_RenderAttachment;
1216 gpu->texture = device.CreateTexture(
reinterpret_cast<const wgpu::TextureDescriptor*
>(&td));
1218 uploadTexturePixelsMips(gpu.get(), rgba,
width,
height);
1220 WGPUTextureViewDescriptor vd{};
1221 vd.format = WGPUTextureFormat_RGBA8Unorm;
1222 vd.dimension = WGPUTextureViewDimension_2D;
1223 vd.baseMipLevel = 0;
1224 vd.mipLevelCount = gpu->mipLevels;
1225 vd.baseArrayLayer = 0;
1226 vd.arrayLayerCount = 1;
1227 gpu->view = gpu->texture.CreateView(
reinterpret_cast<const wgpu::TextureViewDescriptor*
>(&vd));
1228 gpu->sampler = makeSampler(info.
sampler, gpu->mipLevels);
1233 tex->mipmapCount = int(gpu->mipLevels);
1235 tex->gpuHandle = gpu.get();
1237 ownedGpuTextures.push_back(std::move(gpu));
1238 ownedTextures.push_back(std::unique_ptr<Texture>(tex));
1242void Graphics::uploadTexturePixels(
GpuTexture *gt,
const uint8_t *rgba,
int w,
int h,
1245 uploadTexturePixelsMips(gt, rgba,
w,
h);
1248void Graphics::uploadTexturePixelsMips(GpuTexture *gt,
const uint8_t *rgba,
int w,
int h) {
1250 WGPUTexelCopyBufferLayout
layout{};
1252 layout.bytesPerRow =
static_cast<uint32_t
>(
w * 4);
1253 layout.rowsPerImage =
static_cast<uint32_t
>(
h);
1254 WGPUExtent3D extent{
static_cast<uint32_t
>(
w),
static_cast<uint32_t
>(
h), 1};
1256 for (uint32_t
m = 0;
m < gt->mipLevels; ++
m) {
1257 WGPUTexelCopyTextureInfo dst{};
1258 dst.texture = gt->texture.Get();
1260 dst.aspect = WGPUTextureAspect_All;
1261 queue.WriteTexture(
reinterpret_cast<const wgpu::TexelCopyTextureInfo*
>(&dst), rgba,
1262 static_cast<uint64_t
>(
w) *
h * 4,
1263 reinterpret_cast<const wgpu::TexelCopyBufferLayout*
>(&
layout),
1264 reinterpret_cast<const wgpu::Extent3D*
>(&extent));
1265 if (
m + 1 < gt->mipLevels) {
1267 std::vector<uint8_t> next((
w / 2) * (
h / 2) * 4);
1268 for (
int y = 0;
y <
h / 2; ++
y) {
1269 for (
int x = 0;
x <
w / 2; ++
x) {
1270 for (
int c = 0;
c < 4; ++
c) {
1272 acc += rgba[((
y * 2 + 0) *
w + (
x * 2 + 0)) * 4 +
c];
1273 acc += rgba[((
y * 2 + 0) *
w + (
x * 2 + 1)) * 4 +
c];
1274 acc += rgba[((
y * 2 + 1) *
w + (
x * 2 + 0)) * 4 +
c];
1275 acc += rgba[((
y * 2 + 1) *
w + (
x * 2 + 1)) * 4 +
c];
1276 next[(
y * (
w / 2) +
x) * 4 +
c] = uint8_t(acc / 4);
1283 layout.bytesPerRow =
static_cast<uint32_t
>(
w * 4);
1284 layout.rowsPerImage =
static_cast<uint32_t
>(
h);
1285 extent = {
static_cast<uint32_t
>(
w),
static_cast<uint32_t
>(
h), 1};
1292 return newCubemap(faceSize, rgbaFaces, info);
1297 if (faceSize <= 0 || !rgbaFaces)
1298 throw Exception(
"newCubemap: invalid size or null data");
1300 auto gpu = std::make_unique<GpuTexture>();
1301 gpu->width = faceSize;
1302 gpu->height = faceSize;
1303 gpu->samplerState = info.
sampler;
1307 WGPUTextureDescriptor td{};
1308 td.label = sv(
"eve_cubemap");
1309 td.dimension = WGPUTextureDimension_2D;
1310 td.size.width =
static_cast<uint32_t
>(faceSize);
1311 td.size.height =
static_cast<uint32_t
>(faceSize);
1312 td.size.depthOrArrayLayers = 6;
1314 td.format = WGPUTextureFormat_RGBA8Unorm;
1315 td.mipLevelCount = gpu->mipLevels;
1316 td.usage = WGPUTextureUsage_TextureBinding | WGPUTextureUsage_CopyDst;
1317 gpu->texture = device.CreateTexture(
reinterpret_cast<const wgpu::TextureDescriptor*
>(&td));
1319 WGPUTexelCopyBufferLayout
layout{};
1321 layout.bytesPerRow =
static_cast<uint32_t
>(faceSize * 4);
1322 layout.rowsPerImage =
static_cast<uint32_t
>(faceSize);
1323 WGPUExtent3D extent{
static_cast<uint32_t
>(faceSize),
static_cast<uint32_t
>(faceSize), 1};
1324 for (
int f = 0;
f < 6; ++
f) {
1325 WGPUTexelCopyTextureInfo dst{};
1326 dst.texture = gpu->texture.Get();
1328 dst.aspect = WGPUTextureAspect_All;
1329 dst.origin = {0, 0,
static_cast<uint32_t
>(
f)};
1330 queue.WriteTexture(
reinterpret_cast<const wgpu::TexelCopyTextureInfo*
>(&dst),
1331 rgbaFaces +
f * faceSize * faceSize * 4,
1332 static_cast<uint64_t
>(faceSize) * faceSize * 4,
1333 reinterpret_cast<const wgpu::TexelCopyBufferLayout*
>(&
layout),
1334 reinterpret_cast<const wgpu::Extent3D*
>(&extent));
1337 WGPUTextureViewDescriptor vd{};
1338 vd.format = WGPUTextureFormat_RGBA8Unorm;
1339 vd.dimension = WGPUTextureViewDimension_Cube;
1340 vd.baseMipLevel = 0;
1341 vd.mipLevelCount = gpu->mipLevels;
1342 vd.baseArrayLayer = 0;
1343 vd.arrayLayerCount = 6;
1344 gpu->view = gpu->texture.CreateView(
reinterpret_cast<const wgpu::TextureViewDescriptor*
>(&vd));
1345 gpu->sampler = makeSampler(info.
sampler, gpu->mipLevels);
1348 tex->width = faceSize;
1349 tex->height = faceSize;
1350 tex->mipmapCount = int(gpu->mipLevels);
1352 tex->gpuHandle = gpu.get();
1354 ownedGpuTextures.push_back(std::move(gpu));
1355 ownedTextures.push_back(std::unique_ptr<Texture>(tex));
1360 if (!texture)
return;
1361 auto *gpu = gpuForTexture(texture);
1363 gpu->samplerState = sampler;
1364 gpu->sampler = makeSampler(sampler, gpu->mipLevels);
1371 if (filename.empty())
throw Exception(
"newTextureFromFile: empty filename");
1372 auto it = texturesByPath.find(filename);
1373 if (it != texturesByPath.end()) {
1377 auto *imgMod = image::Image::create();
1380 texturesByPath[filename] = tex;
1385 auto it = texturesByPath.find(filename);
1386 if (it == texturesByPath.end())
return false;
1390 auto *imgMod = image::Image::create();
1396 if (!
data)
return false;
1399 auto *gpu = gpuForTexture(tex);
1401 uploadTexturePixelsMips(gpu,
static_cast<const uint8_t *
>(
data->getData()), tex->
width,
1408 if (!texture || !texture->
gpuHandle)
return false;
1411 texture->
gpuHandle == flatNormalTexture3D ||
1412 texture->
gpuHandle == defaultEnvCubemap)
1416 auto gpuIt = std::find_if(ownedGpuTextures.begin(), ownedGpuTextures.end(),
1417 [&](
const std::unique_ptr<GpuTexture> &g) {
1418 return g.get() == gpu;
1420 if (gpuIt == ownedGpuTextures.end())
return false;
1422 auto texIt = std::find_if(ownedTextures.begin(), ownedTextures.end(),
1423 [&](
const std::unique_ptr<Texture> &t) {
1424 return t.get() == texture;
1426 if (texIt == ownedTextures.end())
return false;
1429 for (
auto it = texturesByPath.begin(); it != texturesByPath.end();) {
1430 if (it->second == texture)
1431 it = texturesByPath.erase(it);
1437 ownedGpuTextures.erase(gpuIt);
1439 (void)texIt->release();
1440 ownedTextures.erase(texIt);
1448GpuTexture *Graphics::gpuForTextureOrWhite(
Texture *t)
const {
1449 GpuTexture *g = gpuForTexture(t);
1450 return g ? g : whiteTexture;
1453wgpu::BindGroup Graphics::makeTex2DBindGroup(GpuTexture *
color, GpuTexture *
depth) {
1456 WGPUBindGroupEntry entries[5]{};
1457 entries[0].binding = 0;
1458 entries[0].textureView =
c->
view.Get();
1459 entries[1].binding = 1;
1460 entries[1].textureView =
d->view.Get();
1461 entries[2].binding = 2;
1462 entries[2].sampler =
c->sampler.Get();
1463 entries[3].binding = 3;
1464 entries[3].sampler =
d->sampler.Get();
1465 entries[4].binding = 4;
1466 entries[4].buffer = currentUboArena().buffer.Get();
1469 WGPUBindGroupDescriptor desc{};
1470 desc.label = sv(
"eve_tex2d_group");
1471 desc.layout = tex2DSetLayout.Get();
1472 desc.entryCount = 5;
1473 desc.entries = entries;
1474 return device.CreateBindGroup(
reinterpret_cast<const wgpu::BindGroupDescriptor*
>(&desc));
1477wgpu::BindGroup Graphics::makeMeshBindGroup(GpuTexture *
albedo, GpuTexture *
normal, GpuTexture *env,
1479 uint32_t frameUboOffset, uint32_t shadowUboOffset,
1480 uint32_t pushUboOffset) {
1483 GpuTexture *e = env ? env : defaultEnvCubemap;
1485 GpuTexture *
d =
depth ?
depth : flatDepthTexture3D;
1487 WGPUBindGroupEntry entries[10]{};
1488 entries[0].binding = 0;
1489 entries[0].buffer = currentUboArena().buffer.Get();
1490 entries[0].size =
sizeof(Mesh3DUBO);
1491 entries[1].binding = 1;
1492 entries[1].textureView =
a->view.Get();
1493 entries[2].binding = 2;
1494 entries[2].textureView =
n->view.Get();
1495 entries[3].binding = 3;
1496 entries[3].textureView = e->view.Get();
1497 entries[4].binding = 4;
1498 entries[4].buffer = currentUboArena().buffer.Get();
1499 entries[4].size =
sizeof(ShadowUBO);
1500 entries[5].binding = 5;
1501 entries[5].textureView = shadowDepthArray ? shadowDepthArray->
view.Get() :
nullptr;
1502 entries[6].binding = 6;
1503 entries[6].textureView =
h->view.Get();
1504 entries[7].binding = 7;
1505 entries[7].sampler = mainSampler.Get();
1506 entries[8].binding = 8;
1507 entries[8].sampler = shadowDepthArray ? shadowDepthArray->
sampler.Get() :
nullptr;
1508 entries[9].binding = 9;
1509 entries[9].textureView =
d->view.Get();
1511 (void)frameUboOffset;
1512 (void)shadowUboOffset;
1513 (void)pushUboOffset;
1514 WGPUBindGroupDescriptor desc{};
1515 desc.label = sv(
"eve_mesh_group");
1516 desc.layout = mesh3dSetLayout.Get();
1517 desc.entryCount = 10;
1518 desc.entries = entries;
1519 return device.CreateBindGroup(
reinterpret_cast<const wgpu::BindGroupDescriptor*
>(&desc));
1527 int vertexCount,
const uint32_t *indices,
int indexCount) {
1528 if (vertexCount <= 0 || !posXYZ)
throw Exception(
"newMeshFromArrays: invalid vertex data");
1530 std::vector<float>
verts;
1531 verts.reserve(vertexCount * 8);
1532 for (
int i = 0; i < vertexCount; ++i) {
1533 verts.push_back(posXYZ[i * 3 + 0]);
1534 verts.push_back(posXYZ[i * 3 + 1]);
1535 verts.push_back(posXYZ[i * 3 + 2]);
1537 verts.push_back(nrmXYZ[i * 3 + 0]);
1538 verts.push_back(nrmXYZ[i * 3 + 1]);
1539 verts.push_back(nrmXYZ[i * 3 + 2]);
1541 verts.push_back(0.f);
1542 verts.push_back(0.f);
1543 verts.push_back(1.f);
1546 verts.push_back(uvST[i * 2 + 0]);
1547 verts.push_back(uvST[i * 2 + 1]);
1549 verts.push_back(0.f);
1550 verts.push_back(0.f);
1554 auto gpu = std::make_unique<GpuMesh>();
1555 gpu->vertexCount = uint32_t(vertexCount);
1556 gpu->indexCount = indexCount > 0 ? uint32_t(indexCount) : 0;
1557 gpu->vertexStride = 32;
1559 WGPUBufferDescriptor vbd{};
1560 vbd.label = sv(
"eve_mesh_vb");
1561 vbd.size =
verts.size() *
sizeof(float);
1562 vbd.usage = WGPUBufferUsage_CopyDst | WGPUBufferUsage_Vertex;
1563 vbd.mappedAtCreation =
false;
1564 gpu->vertexBuffer = device.CreateBuffer(
reinterpret_cast<const wgpu::BufferDescriptor*
>(&vbd));
1565 queue.WriteBuffer(gpu->vertexBuffer, 0,
verts.data(), vbd.size);
1567 if (indexCount > 0) {
1569 if (vertexCount <= 65535) {
1570 std::vector<uint16_t> idx16;
1571 idx16.reserve(indexCount);
1572 for (
int i = 0; i < indexCount; ++i) idx16.push_back(uint16_t(indices[i]));
1573 WGPUBufferDescriptor ibd{};
1574 ibd.label = sv(
"eve_mesh_ib");
1575 ibd.size = uint64_t(indexCount) *
sizeof(uint16_t);
1576 ibd.usage = WGPUBufferUsage_CopyDst | WGPUBufferUsage_Index;
1577 ibd.mappedAtCreation =
false;
1578 gpu->indexBuffer = device.CreateBuffer(
reinterpret_cast<const wgpu::BufferDescriptor*
>(&ibd));
1579 queue.WriteBuffer(gpu->indexBuffer, 0, idx16.data(), ibd.size);
1580 gpu->indexFormat = wgpu::IndexFormat::Uint16;
1582 WGPUBufferDescriptor ibd{};
1583 ibd.label = sv(
"eve_mesh_ib");
1584 ibd.size = uint64_t(indexCount) *
sizeof(uint32_t);
1585 ibd.usage = WGPUBufferUsage_CopyDst | WGPUBufferUsage_Index;
1586 ibd.mappedAtCreation =
false;
1587 gpu->indexBuffer = device.CreateBuffer(
reinterpret_cast<const wgpu::BufferDescriptor*
>(&ibd));
1588 queue.WriteBuffer(gpu->indexBuffer, 0, indices, ibd.size);
1589 gpu->indexFormat = wgpu::IndexFormat::Uint32;
1594 mesh->indexCount = indexCount;
1595 mesh->gpuHandle = gpu.get();
1596 mesh->computeBounds(posXYZ, vertexCount);
1597 ownedGpuMeshes.push_back(std::move(gpu));
1598 ownedMeshes.push_back(std::unique_ptr<Mesh>(
mesh));
1608 std::vector<float>
pos, nrm, uv;
1609 std::vector<uint32_t>
idx;
1610 pos.reserve(
mesh.mNumVertices * 3);
1611 nrm.reserve(
mesh.mNumVertices * 3);
1612 uv.reserve(
mesh.mNumVertices * 2);
1613 idx.reserve(
mesh.mNumFaces * 3);
1615 for (
unsigned i = 0; i <
mesh.mNumVertices; ++i) {
1616 aiVector3D
p = worldTransform *
mesh.mVertices[i];
1620 if (
mesh.mNormals) {
1621 aiMatrix3x3 rot(worldTransform);
1622 aiVector3D
n = rot *
mesh.mNormals[i];
1632 if (
mesh.mTextureCoords[0]) {
1633 uv.push_back(
mesh.mTextureCoords[0][i].x);
1634 uv.push_back(
mesh.mTextureCoords[0][i].y);
1640 for (
unsigned f = 0;
f <
mesh.mNumFaces; ++
f) {
1641 for (
unsigned v = 0;
v <
mesh.mFaces[
f].mNumIndices; ++
v)
1642 idx.push_back(
mesh.mFaces[
f].mIndices[
v]);
1646 if (
m &&
mesh.mNumAnimMeshes > 0) {
1647 m->initMorphBase(
int(
mesh.mNumVertices),
pos.data(), nrm.data(), uv.data());
1648 for (
unsigned a = 0;
a <
mesh.mNumAnimMeshes; ++
a) {
1649 const aiAnimMesh *am =
mesh.mAnimMeshes[
a];
1650 std::vector<float> absPos(am->mNumVertices * 3);
1651 for (
unsigned i = 0; i < am->mNumVertices; ++i) {
1652 absPos[i * 3 + 0] = am->mVertices[i].x;
1653 absPos[i * 3 + 1] = am->mVertices[i].y;
1654 absPos[i * 3 + 2] = am->mVertices[i].z;
1656 std::string
name = am->mName.C_Str();
1657 if (
name.empty())
name =
"morph" + std::to_string(
a);
1658 m->addMorphTargetAbsolute(
name, absPos.data());
1665 if (!
mesh || !
mesh->hasMorphData() || !
mesh->isMorphDirty())
return false;
1666 auto *gpu =
static_cast<GpuMesh *
>(
mesh->gpuHandle);
1667 if (!gpu || !gpu->vertexBuffer)
return false;
1668 std::vector<float>
pos, nrm;
1669 mesh->computeMorphedPositions(
pos, nrm);
1670 if (
pos.empty())
return false;
1671 mesh->computeBounds(
pos.data(),
mesh->getVertexCount());
1672 std::vector<float>
verts;
1673 verts.reserve(
mesh->getVertexCount() * 8);
1674 const auto &uvs =
mesh->baseUv();
1675 for (
int i = 0; i <
mesh->getVertexCount(); ++i) {
1679 if (nrm.size() >= size_t((i + 1) * 3)) {
1680 verts.push_back(nrm[i * 3 + 0]);
1681 verts.push_back(nrm[i * 3 + 1]);
1682 verts.push_back(nrm[i * 3 + 2]);
1684 verts.push_back(0.f);
1685 verts.push_back(0.f);
1686 verts.push_back(1.f);
1688 if (uvs.size() >= size_t((i + 1) * 2)) {
1689 verts.push_back(uvs[i * 2 + 0]);
1690 verts.push_back(uvs[i * 2 + 1]);
1692 verts.push_back(0.f);
1693 verts.push_back(0.f);
1696 queue.WriteBuffer(gpu->vertexBuffer, 0,
verts.data(),
verts.size() *
sizeof(float));
1697 mesh->markMorphClean();
1702 const float *uvST,
int vertexCount,
const uint32_t *indices,
1716 if (!
mesh || !
mesh->gpuHandle)
return false;
1718 auto *gpu =
static_cast<GpuMesh *
>(
mesh->gpuHandle);
1719 auto gpuIt = std::find_if(ownedGpuMeshes.begin(), ownedGpuMeshes.end(),
1720 [&](
const std::unique_ptr<GpuMesh> &g) {
1721 return g.get() == gpu;
1723 if (gpuIt == ownedGpuMeshes.end())
return false;
1725 auto meshIt = std::find_if(ownedMeshes.begin(), ownedMeshes.end(),
1726 [&](
const std::unique_ptr<Mesh> &
m) {
1727 return m.get() == mesh;
1729 if (meshIt == ownedMeshes.end())
return false;
1731 mesh->gpuHandle =
nullptr;
1732 ownedGpuMeshes.erase(gpuIt);
1734 (void)meshIt->release();
1735 ownedMeshes.erase(meshIt);
1740 std::vector<float>
pos, nrm, uv;
1741 std::vector<uint32_t>
idx;
1742 for (
int y = 0;
y <= stacks; ++
y) {
1743 for (
int x = 0;
x <= slices; ++
x) {
1744 float u = float(
x) / float(slices);
1745 float v = float(
y) / float(stacks);
1746 float theta =
u * 2.f * glm::pi<float>();
1747 float phi =
v * glm::pi<float>();
1748 float sx = std::sin(phi) * std::cos(theta);
1749 float sy = std::cos(phi);
1750 float sz = std::sin(phi) * std::sin(theta);
1761 for (
int y = 0;
y < stacks; ++
y) {
1762 for (
int x = 0;
x < slices; ++
x) {
1763 int a =
y * (slices + 1) +
x;
1764 int b =
a + slices + 1;
1767 idx.push_back(
a + 1);
1769 idx.push_back(
b + 1);
1770 idx.push_back(
a + 1);
1778 std::vector<float>
pos, nrm, uv;
1779 std::vector<uint32_t>
idx;
1780 for (
int y = 0;
y <= stacks; ++
y) {
1781 for (
int x = 0;
x <= slices; ++
x) {
1782 float u = float(
x) / float(slices);
1783 float v = float(
y) / float(stacks);
1784 float theta =
u * 2.f * glm::pi<float>();
1785 pos.push_back(std::cos(theta));
1786 pos.push_back(
v * 2.f - 1.f);
1787 pos.push_back(std::sin(theta));
1788 nrm.push_back(std::cos(theta));
1790 nrm.push_back(std::sin(theta));
1795 for (
int y = 0;
y < stacks; ++
y) {
1796 for (
int x = 0;
x < slices; ++
x) {
1797 int a =
y * (slices + 1) +
x;
1798 int b =
a + slices + 1;
1801 idx.push_back(
a + 1);
1803 idx.push_back(
b + 1);
1804 idx.push_back(
a + 1);
1808 int base = int(
pos.size() / 3);
1809 pos.push_back(0.f);
pos.push_back(-1.f);
pos.push_back(0.f);
1810 nrm.push_back(0.f); nrm.push_back(-1.f); nrm.push_back(0.f);
1811 uv.push_back(0.5f); uv.push_back(0.5f);
1812 for (
int x = 0;
x < slices; ++
x) {
1813 float theta0 = float(
x) / float(slices) * 2.f * glm::pi<float>();
1814 float theta1 = float(
x + 1) / float(slices) * 2.f * glm::pi<float>();
1815 int i0 = int(
pos.size() / 3);
1816 pos.push_back(std::cos(theta0));
pos.push_back(-1.f);
pos.push_back(std::sin(theta0));
1817 nrm.push_back(0.f); nrm.push_back(-1.f); nrm.push_back(0.f);
1818 uv.push_back(0.5f + 0.5f * std::cos(theta0)); uv.push_back(0.5f + 0.5f * std::sin(theta0));
1819 int i1 = int(
pos.size() / 3);
1820 pos.push_back(std::cos(theta1));
pos.push_back(-1.f);
pos.push_back(std::sin(theta1));
1821 nrm.push_back(0.f); nrm.push_back(-1.f); nrm.push_back(0.f);
1822 uv.push_back(0.5f + 0.5f * std::cos(theta1)); uv.push_back(0.5f + 0.5f * std::sin(theta1));
1823 idx.push_back(base);
1827 base = int(
pos.size() / 3);
1828 pos.push_back(0.f);
pos.push_back(1.f);
pos.push_back(0.f);
1829 nrm.push_back(0.f); nrm.push_back(1.f); nrm.push_back(0.f);
1830 uv.push_back(0.5f); uv.push_back(0.5f);
1831 for (
int x = 0;
x < slices; ++
x) {
1832 float theta0 = float(
x) / float(slices) * 2.f * glm::pi<float>();
1833 float theta1 = float(
x + 1) / float(slices) * 2.f * glm::pi<float>();
1834 int i0 = int(
pos.size() / 3);
1835 pos.push_back(std::cos(theta0));
pos.push_back(1.f);
pos.push_back(std::sin(theta0));
1836 nrm.push_back(0.f); nrm.push_back(1.f); nrm.push_back(0.f);
1837 uv.push_back(0.5f + 0.5f * std::cos(theta0)); uv.push_back(0.5f + 0.5f * std::sin(theta0));
1838 int i1 = int(
pos.size() / 3);
1839 pos.push_back(std::cos(theta1));
pos.push_back(1.f);
pos.push_back(std::sin(theta1));
1840 nrm.push_back(0.f); nrm.push_back(1.f); nrm.push_back(0.f);
1841 uv.push_back(0.5f + 0.5f * std::cos(theta1)); uv.push_back(0.5f + 0.5f * std::sin(theta1));
1842 idx.push_back(base);
1855void Graphics::clear2DBatches() {
1856 solidBatches.clear();
1857 texturedBatches.clear();
1859 overlaySpans.clear();
1860 sceneColorComposited =
false;
1863void Graphics::noteSolidOverlay() {
1864 if (solidBatches.empty())
return;
1865 const uint32_t
idx = uint32_t(solidBatches.size() - 1);
1866 const uint32_t
n = uint32_t(solidBatches.back().batch.vertices().size());
1867 if (!overlaySpans.empty() && overlaySpans.back().kind == OverlayKind::Solid &&
1868 overlaySpans.back().index ==
idx) {
1869 overlaySpans.back().vertCount =
n - overlaySpans.back().vertBegin;
1872 const uint32_t begin =
n >= 6u ?
n - 6u : 0
u;
1873 overlaySpans.push_back({OverlayKind::Solid,
idx, begin,
n - begin});
1876void Graphics::noteTexturedOverlay(Texture *tex) {
1878 const uint32_t
idx = texturedBatches.empty() ? 0
u : uint32_t(texturedBatches.size() - 1);
1879 if (!overlaySpans.empty() && overlaySpans.back().kind == OverlayKind::Textured &&
1880 overlaySpans.back().index ==
idx)
1882 overlaySpans.push_back({OverlayKind::Textured,
idx, 0, 0});
1887 auto it = std::find_if(solidBatches.begin(), solidBatches.end(),
1888 [&](
const SolidBatch &sb) { return sb.blend == blend; });
1889 if (it == solidBatches.end()) {
1891 it = solidBatches.end() - 1;
1899 auto it = std::find_if(solidBatches.begin(), solidBatches.end(),
1900 [&](
const SolidBatch &sb) { return sb.blend == blend; });
1901 if (it == solidBatches.end()) {
1903 it = solidBatches.end() - 1;
1916 drawTexturedRectShaderUV(texture,
shader,
x,
y,
w,
h, 0.f, 0.f, 1.f, 1.f,
color);
1920 float v0,
float u1,
float v1,
const Color &
color) {
1921 drawTexturedRectShaderUV(texture,
currentShader,
x,
y,
w,
h, u0, v0, u1, v1,
color);
1925 float h,
float u0,
float v0,
float u1,
float v1,
1931 if (texturedBatches.empty() || texturedBatches.back().texture != texture ||
1932 texturedBatches.back().shader !=
shader || texturedBatches.back().depth !=
nullptr ||
1933 texturedBatches.back().blend != blend) {
1936 texturedBatches.back().batch.addTexturedRect(
x,
y,
w,
h,
color, u0, v0, u1, v1, rotatedUV);
1937 noteTexturedOverlay(texture);
1941 float w,
float h,
float degrees,
float u0,
float v0,
1948 auto it = std::find_if(texturedBatches.begin(), texturedBatches.end(),
1950 return tb.texture == texture && tb.shader == shader &&
1951 tb.depth == nullptr && tb.blend == blend;
1953 if (it == texturedBatches.end()) {
1955 it = texturedBatches.end() - 1;
1957 it->batch.addTexturedRectRotated(
cx,
cy,
w,
h,
degrees,
color, u0, v0, u1, v1, rotatedUV);
1958 noteTexturedOverlay(texture);
1962 float y,
float w,
float h,
const Color &tint) {
1967 auto it = std::find_if(texturedBatches.begin(), texturedBatches.end(),
1969 return tb.texture == color && tb.shader == shader &&
1972 if (it == texturedBatches.end()) {
1974 it = texturedBatches.end() - 1;
1976 it->batch.addTexturedRect(
x,
y,
w,
h, tint, 0.f, 0.f, 1.f, 1.f,
false);
1977 noteTexturedOverlay(
color);
1981 float h,
float u0,
float v0,
float u1,
float v1,
1987 auto it = std::find_if(litBatches.begin(), litBatches.end(),
1989 return lb.albedo == albedo && lb.normal == normal;
1991 if (it == litBatches.end()) {
1993 it = litBatches.end() - 1;
1995 it->batch.addTexturedRect(
x,
y,
w,
h,
color, u0, v0, u1, v1,
false);
2000 lighting2dFrame = ubo;
2003void Graphics::flush2D(wgpu::RenderPassEncoder pass,
int viewW,
int viewH,
2004 WGPUTextureFormat format) {
2005 auto spans = std::move(overlaySpans);
2006 const bool offscreen = uint32_t(format) != uint32_t(surfaceFormat);
2008 struct SolidUpload {
2010 uint64_t offset = 0;
2013 std::vector<SolidUpload> solidUploads;
2014 solidUploads.reserve(solidBatches.size());
2015 for (
const auto &sb : solidBatches) {
2016 if (sb.batch.empty()) {
2017 solidUploads.push_back(SolidUpload{sb.blend, 0, 0});
2020 Batcher ndc = sb.batch;
2021 ndc.toNDC(viewW, viewH);
2022 auto verts = ndc.vertices();
2027 std::vector<float>
data;
2029 for (
const auto &
v :
verts) {
2030 data.push_back(
v.pos.x);
2031 data.push_back(
v.pos.y);
2032 data.push_back(
v.color.r);
2033 data.push_back(
v.color.g);
2034 data.push_back(
v.color.b);
2035 data.push_back(
v.color.a);
2037 const uint64_t bytes =
data.size() *
sizeof(float);
2038 auto &arena = currentVertexArena();
2039 ensureVertexArena(arena, arena.used + bytes);
2040 const uint64_t offset = arena.alloc(bytes);
2041 queue.WriteBuffer(arena.buffer, offset,
data.data(), bytes);
2042 solidUploads.push_back(SolidUpload{sb.blend, offset, bytes});
2045 auto solidPipe = [&](
BlendMode mode) -> wgpu::RenderPipeline {
2048 return offscreen ? offscreenColorAdditivePipeline : colorAdditivePipeline;
2050 return offscreen ? offscreenColorOpaquePipeline : colorOpaquePipeline;
2053 return offscreen ? offscreenColorPipeline : colorPipeline;
2057 auto drawSolid = [&](uint32_t batchIndex, uint32_t first, uint32_t count) {
2058 if (batchIndex >= solidUploads.size())
return;
2059 const SolidUpload &
u = solidUploads[batchIndex];
2060 if (
u.bytes == 0 || count == 0)
return;
2061 pass.SetPipeline(solidPipe(
u.blend));
2062 pass.SetVertexBuffer(0, currentVertexArena().buffer,
u.offset,
u.bytes);
2063 pass.Draw(count, 1, first, 0);
2066 if (!spans.empty()) {
2067 for (
const auto &sp : spans) {
2068 if (sp.kind == OverlayKind::Solid)
2069 drawSolid(sp.index, sp.vertBegin, sp.vertCount);
2070 else if (sp.kind == OverlayKind::Textured && sp.index < texturedBatches.size())
2071 drawTexturedBatch(pass, texturedBatches[sp.index], viewW, viewH, format, offscreen);
2072 else if (sp.kind == OverlayKind::Lit && sp.index < litBatches.size())
2073 drawLitBatch(pass, litBatches[sp.index], viewW, viewH, format);
2076 for (
size_t i = 0; i < solidUploads.size(); ++i) {
2077 if (solidUploads[i].bytes > 0)
2078 drawSolid(uint32_t(i), 0, uint32_t(solidBatches[i].batch.vertices().size()));
2080 for (
auto &
tb : texturedBatches) {
2081 if (
tb.batch.empty())
continue;
2082 drawTexturedBatch(pass,
tb, viewW, viewH, format, offscreen);
2084 for (
auto &lb : litBatches) {
2085 if (lb.batch.empty())
continue;
2086 drawLitBatch(pass, lb, viewW, viewH, format);
2092void Graphics::drawTexturedBatch(wgpu::RenderPassEncoder pass, TexturedBatch &
tb,
int viewW,
2093 int viewH, WGPUTextureFormat format,
bool offscreen) {
2094 Batcher ndc =
tb.batch;
2095 ndc.toNDC(viewW, viewH);
2096 auto verts = ndc.vertices();
2098 if (
verts.empty())
return;
2100 std::vector<float>
data;
2102 for (
const auto &
v :
verts) {
2103 data.push_back(
v.pos.x);
2104 data.push_back(
v.pos.y);
2105 data.push_back(
v.color.r);
2106 data.push_back(
v.color.g);
2107 data.push_back(
v.color.b);
2108 data.push_back(
v.color.a);
2109 data.push_back(
v.uv.x);
2110 data.push_back(
v.uv.y);
2112 uint64_t bytes =
data.size() *
sizeof(float);
2113 auto &arena = currentVertexArena();
2114 ensureVertexArena(arena, arena.used + bytes);
2115 uint64_t vtxOffset = arena.alloc(bytes);
2116 queue.WriteBuffer(arena.buffer, vtxOffset,
data.data(), bytes);
2118 GpuTexture *gpu = gpuForTexture(
tb.texture);
2119 GpuTexture *depthGpu = gpuForTexture(
tb.depth);
2120 auto &uboArena = currentUboArena();
2121 ensureUboArena(uboArena, uboArena.used + 256);
2124 uint32_t pushOffset = 0;
2125 if (
tb.shader &&
tb.shader->pushConstantSize() > 0) {
2127 queue.WriteBuffer(uboArena.buffer, pushOffset,
tb.shader->pushConstantData(),
2131 wgpu::BindGroup bg = makeTex2DBindGroup(gpu, depthGpu);
2132 uint32_t offsets[1] = {pushOffset};
2134 wgpu::RenderPipeline pipe;
2135 if (
tb.shader &&
tb.shader->gpuHandle) {
2136 auto *gs =
static_cast<GpuShader *
>(
tb.shader->gpuHandle);
2137 pipe = offscreen ? gs->offscreenPipeline : gs->swapchainPipeline;
2141 pipe = offscreen ? offscreenTexturedAdditivePipeline : texturedAdditivePipeline;
2144 pipe = offscreen ? offscreenTexturedOpaquePipeline : texturedOpaquePipeline;
2148 pipe = offscreen ? offscreenTexturedPipeline : texturedPipeline;
2153 pass.SetPipeline(pipe);
2154 pass.SetBindGroup(0, bg, 1, offsets);
2155 pass.SetVertexBuffer(0, arena.buffer, vtxOffset, bytes);
2156 pass.Draw(
static_cast<uint32_t
>(
verts.size()), 1, 0, 0);
2159void Graphics::drawLitBatch(wgpu::RenderPassEncoder pass, LitBatch &lb,
int viewW,
int viewH,
2160 WGPUTextureFormat format) {
2161 Batcher ndc = lb.batch;
2162 ndc.toNDC(viewW, viewH);
2163 auto verts = ndc.vertices();
2165 if (
verts.empty())
return;
2167 std::vector<float>
data;
2169 for (
const auto &
v :
verts) {
2170 data.push_back(
v.pos.x);
2171 data.push_back(
v.pos.y);
2172 data.push_back(
v.color.r);
2173 data.push_back(
v.color.g);
2174 data.push_back(
v.color.b);
2175 data.push_back(
v.color.a);
2176 data.push_back(
v.uv.x);
2177 data.push_back(
v.uv.y);
2179 uint64_t bytes =
data.size() *
sizeof(float);
2180 auto &arena = currentVertexArena();
2181 ensureVertexArena(arena, arena.used + bytes);
2182 uint64_t vtxOffset = arena.alloc(bytes);
2183 queue.WriteBuffer(arena.buffer, vtxOffset,
data.data(), bytes);
2185 GpuTexture *albedoGpu = gpuForTextureOrWhite(lb.albedo);
2186 GpuTexture *normalGpu = gpuForTextureOrWhite(lb.normal);
2188 auto &uboArena = currentUboArena();
2189 ensureUboArena(uboArena, uboArena.used + 512);
2190 uint32_t uboOffset = uboArena.alloc(
sizeof(Lighting2DUBO), 256);
2191 queue.WriteBuffer(uboArena.buffer, uboOffset, &lighting2dFrame,
sizeof(Lighting2DUBO));
2193 wgpu::BindGroup bg = makeTex2DBindGroup(albedoGpu, normalGpu);
2194 uint32_t offsets[1] = {uboOffset};
2195 wgpu::RenderPipeline pipe =
2196 uint32_t(format) == uint32_t(surfaceFormat) ? lit2dPipeline : offscreenLitPipeline;
2198 pass.SetPipeline(pipe);
2199 pass.SetBindGroup(0, bg, 1, offsets);
2200 pass.SetVertexBuffer(0, arena.buffer, vtxOffset, bytes);
2201 pass.Draw(
static_cast<uint32_t
>(
verts.size()), 1, 0, 0);
2209 if (!initialized || !device)
return;
2210 frame3DStarted =
true;
2211 frameHad3DThisFrame =
false;
2216 sceneColorPassOpen =
false;
2217 mesh3dDraws.clear();
2218 shadowPassDraws.clear();
2221 if (surfaceNeedsRecreate.load()) {
2222 surfaceNeedsRecreate.store(
false);
2223 markSwapchainDirty();
2225 rebuildSwapchainIfNeeded();
2229 throw eve::Exception(
"begin3DFrameToCanvas: not supported on the webgpu backend");
2248 if (!
mesh || !
mesh->gpuHandle)
return;
2249 frameHad3DThisFrame =
true;
2253 d.texture = texture;
2257 mesh3dDraws.push_back(
d);
2266 mesh3dSceneDepthTexture =
depth;
2273 mesh3dTexBombScale = cellScale;
2274 mesh3dTexBombStrength = strength;
2275 mesh3dTexBombRot = rotAmount;
2278 mesh3dParallaxScale =
scale;
2279 mesh3dParallaxMin = minLayers;
2280 mesh3dParallaxMax = maxLayers;
2284 float windAngle,
float coverage,
float detail) {
2285 mesh3dCloud = glm::vec4(std::clamp(strength, 0.f, 1.f), std::max(worldCell, 1e-4f), time, 0.f);
2286 mesh3dCloudWind = glm::vec4(std::cos(windAngle) * windSpeed, std::sin(windAngle) * windSpeed,
2287 std::clamp(coverage, 0.f, 1.f), std::clamp(detail, 0.f, 1.f));
2290 mesh3dClustered = upload;
2291 mesh3dClusteredActive = upload.
active;
2300 mesh3dEnvTexture = cube;
2301 mesh3dEnvIntensity = intensity;
2307 shadowPassCascade = cascadeIndex;
2308 shadowPassDraws.clear();
2312 if (!
mesh || !
mesh->gpuHandle)
return;
2316 shadowPassDraws.push_back(
d);
2328 shadowPassCascade = -1;
2329 shadowPassDraws.clear();
2332 shadowCascadeDraws[shadowPassCascade] = shadowPassDraws;
2333 shadowPassDraws.clear();
2334 shadowPassCascade = -1;
2342 if (!device)
return;
2344 gbufferPassActive =
true;
2345 gbufferPassPending =
true;
2346 gbufferPassDraws.clear();
2350 float farZ,
Texture *
albedo,
float tintR,
float tintG,
float tintB) {
2351 if (!
mesh || !
mesh->gpuHandle)
return;
2359 d.tint = glm::vec4(tintR, tintG, tintB, 1.f);
2360 gbufferPassDraws.push_back(
d);
2365 float tintG,
float tintB) {
2372 gbufferPassActive =
false;
2376 GbufferSlot &slot = gbufferSlots[currentFrameSlot()];
2377 renderControl_->getGBuffer()->setTargets(gbufferWidth, gbufferHeight, &slot.depthColorTex,
2378 &slot.normalTex, &slot.albedoTex, &slot.depthTex);
2387 float originY,
float originZ,
const std::string &faceDir,
2388 Texture *atlas,
int tilesPerRow,
const uint32_t *ao) {
2389 if (!device || count <= 0 || !packed)
return;
2390 if (!voxelUnitQuadVerts)
return;
2393 frameHad3DThisFrame =
true;
2397 if (faceDir ==
"posX" || faceDir ==
"+x") face = 0;
2398 else if (faceDir ==
"negX" || faceDir ==
"-x") face = 1;
2399 else if (faceDir ==
"posY" || faceDir ==
"+y") face = 2;
2400 else if (faceDir ==
"negY" || faceDir ==
"-y") face = 3;
2401 else if (faceDir ==
"posZ" || faceDir ==
"+z") face = 4;
2404 d.count = uint32_t(count);
2405 d.atlas = gpuForTextureOrWhite(atlas);
2406 d.viewProj = mesh3dViewProj;
2407 d.chunkOrigin = glm::vec4(originX, originY, originZ,
float(face));
2408 d.atlasInfo = glm::vec4(
float(tilesPerRow), 0.f, 0.f, 0.f);
2409 d.tint = glm::vec4(1.f);
2410 d.instanceBufferOffset = 0;
2411 d.pushUboOffset = 0;
2414 auto &arena = voxelInstanceArena;
2415 if (!arena.buffer) {
2416 WGPUBufferDescriptor bd{};
2417 bd.label = sv(
"eve_voxel_instances");
2419 bd.usage = WGPUBufferUsage_CopyDst | WGPUBufferUsage_Vertex;
2420 bd.mappedAtCreation =
false;
2421 arena.buffer = device.CreateBuffer(
reinterpret_cast<const wgpu::BufferDescriptor*
>(&bd));
2422 arena.capacity = 1u << 20;
2424 uint64_t need = uint64_t(count) * 4;
2425 if (arena.used + need > arena.capacity) {
2426 uint64_t cap = arena.capacity * 2;
2427 WGPUBufferDescriptor bd{};
2428 bd.label = sv(
"eve_voxel_instances");
2430 bd.usage = WGPUBufferUsage_CopyDst | WGPUBufferUsage_Vertex;
2431 bd.mappedAtCreation =
false;
2432 arena.buffer = device.CreateBuffer(
reinterpret_cast<const wgpu::BufferDescriptor*
>(&bd));
2433 arena.capacity = cap;
2436 d.instanceBufferOffset =
static_cast<uint32_t
>(arena.used);
2437 queue.WriteBuffer(arena.buffer, arena.used, packed, need);
2439 voxelDraws.push_back(
d);
2446void Graphics::createSceneColorResources(
int width,
int height) {
2447 if (!device)
return;
2448 if (sceneColorWidth ==
width && sceneColorHeight ==
height && !sceneColorSlots.empty())
return;
2450 destroySceneColorResources();
2451 sceneColorWidth =
width;
2452 sceneColorHeight =
height;
2453 sceneColorSamples = 1;
2454 for (
int s = 0;
s < int(kFramesInFlight); ++
s) {
2455 SceneColorSlot slot;
2456 slot.sampleCount = sceneColorSamples;
2458 WGPUTextureDescriptor cd{};
2459 cd.label = sv(
"eve_scene_color");
2460 cd.dimension = WGPUTextureDimension_2D;
2461 cd.size = {
static_cast<uint32_t
>(
width),
static_cast<uint32_t
>(
height), 1};
2462 cd.sampleCount = sceneColorSamples;
2463 cd.format = sceneColorFormat;
2464 cd.mipLevelCount = 1;
2465 cd.usage = WGPUTextureUsage_TextureBinding | WGPUTextureUsage_RenderAttachment |
2466 WGPUTextureUsage_CopySrc;
2467 slot.color = device.CreateTexture(
reinterpret_cast<const wgpu::TextureDescriptor*
>(&cd));
2468 slot.colorView = slot.color.CreateView();
2470 WGPUTextureDescriptor dd{};
2471 dd.label = sv(
"eve_scene_depth");
2472 dd.dimension = WGPUTextureDimension_2D;
2473 dd.size = {
static_cast<uint32_t
>(
width),
static_cast<uint32_t
>(
height), 1};
2474 dd.sampleCount = sceneColorSamples;
2475 dd.format = WGPUTextureFormat_Depth32Float;
2476 dd.mipLevelCount = 1;
2477 dd.usage = WGPUTextureUsage_RenderAttachment;
2478 slot.depth = device.CreateTexture(
reinterpret_cast<const wgpu::TextureDescriptor*
>(&dd));
2479 slot.depthView = slot.depth.CreateView();
2481 slot.colorGpu.texture = slot.color;
2482 slot.colorGpu.view = slot.colorView;
2483 slot.colorGpu.width =
width;
2484 slot.colorGpu.height =
height;
2485 slot.colorGpu.sampler = createLinearSampler(device);
2488 slot.colorTex.gpuHandle = &slot.colorGpu;
2489 slot.colorTex.width =
width;
2490 slot.colorTex.height =
height;
2491 slot.colorTex.mipmapCount = 1;
2493 sceneColorSlots.push_back(std::move(slot));
2495 sceneColorTexture = &sceneColorSlots[0].colorTex;
2498void Graphics::destroySceneColorResources() {
2499 sceneColorSlots.clear();
2500 sceneColorTexture =
nullptr;
2503void Graphics::createShadowResources() {
2504 if (!device)
return;
2505 if (shadowDepthArray)
return;
2509 auto *gpu =
new GpuTexture();
2510 WGPUTextureDescriptor td{};
2511 td.label = sv(
"eve_shadow_depth");
2512 td.dimension = WGPUTextureDimension_2D;
2513 td.size = {
static_cast<uint32_t
>(shadowMapSize),
static_cast<uint32_t
>(shadowMapSize), 3};
2515 td.format = WGPUTextureFormat_Depth32Float;
2516 td.mipLevelCount = 1;
2517 td.usage = WGPUTextureUsage_TextureBinding | WGPUTextureUsage_RenderAttachment;
2518 gpu->texture = device.CreateTexture(
reinterpret_cast<const wgpu::TextureDescriptor*
>(&td));
2520 WGPUTextureViewDescriptor avd{};
2521 avd.format = WGPUTextureFormat_Depth32Float;
2522 avd.dimension = WGPUTextureViewDimension_2DArray;
2523 avd.baseMipLevel = 0;
2524 avd.mipLevelCount = 1;
2525 avd.baseArrayLayer = 0;
2526 avd.arrayLayerCount = 3;
2527 gpu->view = gpu->texture.CreateView(
reinterpret_cast<const wgpu::TextureViewDescriptor*
>(&avd));
2529 WGPUSamplerDescriptor sd{};
2530 sd.label = sv(
"eve_shadow_sampler");
2531 sd.addressModeU = WGPUAddressMode_ClampToEdge;
2532 sd.addressModeV = WGPUAddressMode_ClampToEdge;
2533 sd.addressModeW = WGPUAddressMode_ClampToEdge;
2534 sd.magFilter = WGPUFilterMode_Linear;
2535 sd.minFilter = WGPUFilterMode_Linear;
2536 sd.mipmapFilter = WGPUMipmapFilterMode_Nearest;
2537 sd.compare = WGPUCompareFunction_LessEqual;
2538 sd.maxAnisotropy = 1.f;
2539 gpu->sampler = device.CreateSampler(
reinterpret_cast<const wgpu::SamplerDescriptor*
>(&sd));
2540 shadowDepthArray = gpu;
2543void Graphics::destroyShadowResources() {
2544 shadowDepthArray =
nullptr;
2547void Graphics::createGbufferResources(
int width,
int height) {
2548 if (!device)
return;
2549 if (gbufferWidth ==
width && gbufferHeight ==
height && !gbufferSlots.empty())
return;
2550 destroyGbufferResources();
2551 gbufferWidth =
width;
2554 WGPUTextureDescriptor td{};
2555 td.label = sv(
"eve_gbuffer_target");
2556 td.dimension = WGPUTextureDimension_2D;
2557 td.size = {
static_cast<uint32_t
>(
width),
static_cast<uint32_t
>(
height), 1};
2559 td.format = WGPUTextureFormat_RGBA8Unorm;
2560 td.mipLevelCount = 1;
2561 td.usage = WGPUTextureUsage_TextureBinding | WGPUTextureUsage_RenderAttachment;
2563 WGPUTextureDescriptor dd{};
2564 dd.label = sv(
"eve_gbuffer_depth");
2565 dd.dimension = WGPUTextureDimension_2D;
2566 dd.size = {
static_cast<uint32_t
>(
width),
static_cast<uint32_t
>(
height), 1};
2568 dd.format = WGPUTextureFormat_Depth32Float;
2569 dd.mipLevelCount = 1;
2571 dd.usage = WGPUTextureUsage_TextureBinding | WGPUTextureUsage_RenderAttachment;
2573 for (
int s = 0;
s < int(kFramesInFlight); ++
s) {
2575 slot.normal = device.CreateTexture(
reinterpret_cast<const wgpu::TextureDescriptor*
>(&td));
2576 slot.normalView = slot.normal.CreateView();
2577 slot.depthColor = device.CreateTexture(
reinterpret_cast<const wgpu::TextureDescriptor*
>(&td));
2578 slot.depthColorView = slot.depthColor.CreateView();
2579 slot.albedo = device.CreateTexture(
reinterpret_cast<const wgpu::TextureDescriptor*
>(&td));
2580 slot.albedoView = slot.albedo.CreateView();
2581 slot.depth = device.CreateTexture(
reinterpret_cast<const wgpu::TextureDescriptor*
>(&dd));
2582 slot.depthView = slot.depth.CreateView();
2584 slot.normalGpu.texture = slot.normal;
2585 slot.normalGpu.view = slot.normalView;
2586 slot.normalGpu.sampler = createLinearSampler(device);
2587 slot.depthColorGpu.texture = slot.depthColor;
2588 slot.depthColorGpu.view = slot.depthColorView;
2589 slot.depthColorGpu.sampler = createLinearSampler(device);
2590 slot.albedoGpu.texture = slot.albedo;
2591 slot.albedoGpu.view = slot.albedoView;
2592 slot.albedoGpu.sampler = createLinearSampler(device);
2593 slot.depthGpu.texture = slot.depth;
2594 slot.depthGpu.view = slot.depthView;
2595 slot.depthGpu.sampler = createLinearSampler(device);
2597 slot.normalTex.gpuHandle = &slot.normalGpu;
2598 slot.normalTex.width =
width;
2599 slot.normalTex.height =
height;
2600 slot.depthColorTex.gpuHandle = &slot.depthColorGpu;
2601 slot.depthColorTex.width =
width;
2602 slot.depthColorTex.height =
height;
2603 slot.albedoTex.gpuHandle = &slot.albedoGpu;
2604 slot.albedoTex.width =
width;
2605 slot.albedoTex.height =
height;
2606 slot.depthTex.gpuHandle = &slot.depthGpu;
2607 slot.depthTex.width =
width;
2608 slot.depthTex.height =
height;
2610 gbufferSlots.push_back(std::move(slot));
2614void Graphics::destroyGbufferResources() { gbufferSlots.clear(); }
2620void Graphics::flushMesh3D(wgpu::RenderPassEncoder pass, WGPUTextureFormat format) {
2621 if (mesh3dDraws.empty())
return;
2623 auto &uboArena = currentUboArena();
2624 ensureUboArena(uboArena, uboArena.used + mesh3dDraws.size() * 2048);
2625 auto &vtxArena = currentVertexArena();
2628 for (
auto &
d : mesh3dDraws) {
2629 d.frameUboOffset = uboArena.alloc(
sizeof(Mesh3DUBO), 256);
2630 d.shadowUboOffset = uboArena.alloc(
sizeof(ShadowUBO), 256);
2631 d.pushUboOffset = 0;
2632 if (
d.shader &&
d.shader->pushConstantSize() > 0)
2637 for (
auto &
d : mesh3dDraws) {
2639 ubo.mvp = mesh3dViewProj *
d.model;
2640 ubo.model =
d.model;
2641 ubo.lightDir = glm::vec4(glm::vec3(mesh3dLighting.
lights[0].
posRadius),
float(mesh3dLighting.
count));
2644 ubo.cameraPos = glm::vec4(mesh3dCameraPos, mesh3dRoughness);
2645 ubo.ambient = glm::vec4(glm::vec3(mesh3dLighting.
ambient), mesh3dMetallic);
2647 ubo.lights[i] = mesh3dLighting.
lights[i];
2648 ubo.texBomb = glm::vec4(mesh3dTexBombScale, mesh3dTexBombStrength, mesh3dTexBombRot, 0.f);
2649 ubo.parallax = glm::vec4(mesh3dParallaxScale, mesh3dParallaxMin, mesh3dParallaxMax, 0.f);
2650 ubo.view = mesh3dView;
2651 ubo.clipInfo = glm::vec4(mesh3dNear, mesh3dFar, 0.f, 0.f);
2652 ubo.cloud = mesh3dCloud;
2653 ubo.cloudWind = mesh3dCloudWind;
2654 ubo.lightColor.w = mesh3dEnvIntensity;
2658 if (
d.shader &&
d.shader->isXray() &&
d.shader->pushConstantSize() >= 9 *
sizeof(
float)) {
2659 const float *pc =
d.shader->pushConstantData();
2660 ubo.texBomb = glm::vec4(pc[0], pc[1], pc[2], pc[8]);
2661 ubo.parallax = glm::vec4(pc[3], pc[4], pc[5], pc[7]);
2662 ubo.clipInfo.z = pc[6];
2664 queue.WriteBuffer(uboArena.buffer,
d.frameUboOffset, &ubo,
sizeof(ubo));
2668 ShadowUBO shadowUbo = mesh3dShadows.
ubo;
2669 if (!mesh3dShadows.
active || !mesh3dShadowReceive) shadowUbo.
bias.y = 0.f;
2670 for (
auto &
d : mesh3dDraws)
2671 queue.WriteBuffer(uboArena.buffer,
d.shadowUboOffset, &shadowUbo, sizeof(shadowUbo));
2673 for (
auto &
d : mesh3dDraws) {
2674 auto *gpuMesh =
static_cast<GpuMesh *
>(
d.mesh->gpuHandle);
2675 if (!gpuMesh || !gpuMesh->vertexBuffer)
continue;
2677 wgpu::RenderPipeline pipe = mesh3dPipeline;
2678 if (
d.shader &&
d.shader->gpuHandle) {
2679 auto *gs =
static_cast<GpuShader *
>(
d.shader->gpuHandle);
2680 if (gs->isMesh3D && gs->mesh3dPipeline) {
2681 if (
d.shader->isXray() && gs->mesh3dXrayPipeline)
2682 pipe = gs->mesh3dXrayPipeline;
2684 pipe = gs->mesh3dPipeline;
2687 if (!pipe)
continue;
2688 pass.SetPipeline(pipe);
2690 GpuTexture *
albedo = gpuForTexture(
d.texture);
2691 GpuTexture *
normal = gpuForTexture(mesh3dNormalTexture);
2692 GpuTexture *env = gpuForTexture(mesh3dEnvTexture);
2693 GpuTexture *
height = gpuForTexture(mesh3dHeightTexture);
2694 GpuTexture *
depth = mesh3dSceneDepthTexture ? gpuForTexture(mesh3dSceneDepthTexture)
2695 : flatDepthTexture3D;
2697 d.frameUboOffset,
d.shadowUboOffset,
2699 uint32_t offsets[2] = {
d.frameUboOffset,
d.shadowUboOffset};
2700 pass.SetBindGroup(0, bg, 2, offsets);
2702 if (gpuMesh->indexBuffer) {
2703 pass.SetVertexBuffer(0, gpuMesh->vertexBuffer, 0, gpuMesh->vertexCount * 32);
2704 pass.SetIndexBuffer(gpuMesh->indexBuffer, gpuMesh->indexFormat, 0,
2705 uint64_t(gpuMesh->indexCount) * 4);
2706 pass.DrawIndexed(gpuMesh->indexCount, 1, 0, 0, 0);
2708 pass.SetVertexBuffer(0, gpuMesh->vertexBuffer, 0, gpuMesh->vertexCount * 32);
2709 pass.Draw(gpuMesh->vertexCount, 1, 0, 0);
2712 mesh3dDraws.clear();
2715void Graphics::flushShadowPass(wgpu::RenderPassEncoder pass) {
2716 auto &uboArena = currentUboArena();
2717 ensureUboArena(uboArena, uboArena.used + 4096);
2718 pass.SetPipeline(mesh3dShadowPipeline);
2721 if (shadowCascadeDraws[
c].empty())
continue;
2722 for (
auto &
d : shadowCascadeDraws[
c]) {
2723 auto *gpuMesh =
static_cast<GpuMesh *
>(
d.mesh->gpuHandle);
2724 if (!gpuMesh || !gpuMesh->vertexBuffer)
continue;
2726 uint32_t offset = uboArena.alloc(256, 256);
2727 queue.WriteBuffer(uboArena.buffer, offset, &
d.mvp,
sizeof(glm::mat4));
2729 WGPUBindGroupEntry entry{};
2731 entry.buffer = uboArena.buffer.Get();
2733 WGPUBindGroupDescriptor bgd{};
2734 bgd.layout = shadowSetLayout.Get();
2736 bgd.entries = &entry;
2737 wgpu::BindGroup bg = device.CreateBindGroup(
reinterpret_cast<const wgpu::BindGroupDescriptor*
>(&bgd));
2738 uint32_t offsets[1] = {offset};
2739 pass.SetBindGroup(0, bg, 1, offsets);
2741 if (gpuMesh->indexBuffer) {
2742 pass.SetVertexBuffer(0, gpuMesh->vertexBuffer, 0, gpuMesh->vertexCount * 32);
2743 pass.SetIndexBuffer(gpuMesh->indexBuffer, gpuMesh->indexFormat, 0,
2744 uint64_t(gpuMesh->indexCount) * 4);
2745 pass.DrawIndexed(gpuMesh->indexCount, 1, 0, 0, 0);
2747 pass.SetVertexBuffer(0, gpuMesh->vertexBuffer, 0, gpuMesh->vertexCount * 32);
2748 pass.Draw(gpuMesh->vertexCount, 1, 0, 0);
2751 shadowCascadeDraws[
c].clear();
2755void Graphics::flushGbufferPass(wgpu::RenderPassEncoder pass) {
2756 if (gbufferPassDraws.empty() || gbufferSlots.empty())
return;
2757 auto &uboArena = currentUboArena();
2758 ensureUboArena(uboArena, uboArena.used + gbufferPassDraws.size() * 512);
2759 pass.SetPipeline(mesh3dGbufferPipeline);
2761 for (
auto &
d : gbufferPassDraws) {
2762 auto *gpuMesh =
static_cast<GpuMesh *
>(
d.mesh->gpuHandle);
2763 if (!gpuMesh || !gpuMesh->vertexBuffer)
continue;
2765 struct GbufferPush {
2771 push.model =
d.model;
2772 push.clip = glm::vec4(
d.nearZ,
d.farZ, 0.f, 0.f);
2774 uint32_t offset = uboArena.alloc(256, 256);
2775 queue.WriteBuffer(uboArena.buffer, offset, &
push,
sizeof(
push));
2777 GpuTexture *
albedo = gpuForTextureOrWhite(
d.albedo);
2778 WGPUBindGroupEntry entries[3]{};
2779 entries[0].binding = 0;
2780 entries[0].buffer = uboArena.buffer.Get();
2781 entries[0].size = 128;
2782 entries[1].binding = 1;
2783 entries[1].textureView =
albedo->view.Get();
2784 entries[2].binding = 2;
2785 entries[2].sampler =
albedo->sampler.Get();
2786 WGPUBindGroupDescriptor bgd{};
2787 bgd.layout = gbufferSetLayout.Get();
2789 bgd.entries = entries;
2790 wgpu::BindGroup bg = device.CreateBindGroup(
reinterpret_cast<const wgpu::BindGroupDescriptor*
>(&bgd));
2791 uint32_t offsets[1] = {offset};
2792 pass.SetBindGroup(0, bg, 1, offsets);
2794 if (gpuMesh->indexBuffer) {
2795 pass.SetVertexBuffer(0, gpuMesh->vertexBuffer, 0, gpuMesh->vertexCount * 32);
2796 pass.SetIndexBuffer(gpuMesh->indexBuffer, gpuMesh->indexFormat, 0,
2797 uint64_t(gpuMesh->indexCount) * 4);
2798 pass.DrawIndexed(gpuMesh->indexCount, 1, 0, 0, 0);
2800 pass.SetVertexBuffer(0, gpuMesh->vertexBuffer, 0, gpuMesh->vertexCount * 32);
2801 pass.Draw(gpuMesh->vertexCount, 1, 0, 0);
2804 gbufferPassDraws.clear();
2805 gbufferPassPending =
false;
2808void Graphics::flushVoxelDraws(wgpu::RenderPassEncoder pass, WGPUTextureFormat format) {
2809 if (voxelDraws.empty() || !voxelRectPipeline)
return;
2810 auto &uboArena = currentUboArena();
2811 ensureUboArena(uboArena, uboArena.used + voxelDraws.size() * 512);
2812 pass.SetPipeline(voxelRectPipeline);
2814 for (
auto &
d : voxelDraws) {
2815 uint32_t offset = uboArena.alloc(256, 256);
2818 glm::vec4 chunkOrigin;
2819 glm::vec4 atlasInfo;
2822 pc.viewProj =
d.viewProj;
2823 pc.chunkOrigin =
d.chunkOrigin;
2824 pc.atlasInfo =
d.atlasInfo;
2826 queue.WriteBuffer(uboArena.buffer, offset, &pc,
sizeof(pc));
2828 WGPUBindGroupEntry entries[3]{};
2829 entries[0].binding = 0;
2830 entries[0].buffer = uboArena.buffer.Get();
2831 entries[0].size =
sizeof(pc);
2832 entries[1].binding = 1;
2833 entries[1].textureView =
d.atlas->view.Get();
2834 entries[2].binding = 2;
2835 entries[2].sampler =
d.atlas->sampler.Get();
2836 WGPUBindGroupDescriptor bgd{};
2837 bgd.layout = voxelSetLayout.Get();
2839 bgd.entries = entries;
2840 wgpu::BindGroup bg = device.CreateBindGroup(
reinterpret_cast<const wgpu::BindGroupDescriptor*
>(&bgd));
2841 uint32_t offsets[1] = {offset};
2842 pass.SetBindGroup(0, bg, 1, offsets);
2844 pass.SetVertexBuffer(0, voxelUnitQuadVerts, 0, 32);
2845 pass.SetVertexBuffer(1, voxelInstanceArena.buffer,
d.instanceBufferOffset,
2846 uint64_t(
d.count) * 4);
2847 pass.SetIndexBuffer(voxelUnitQuadIndices, wgpu::IndexFormat::Uint32, 0, 24);
2848 pass.DrawIndexed(6,
d.count, 0, 0, 0);
2857bool Graphics::acquireSurfaceTexture(wgpu::TextureView &
view, wgpu::Texture &texture) {
2858 if (!surface || !device || !swapchainConfigured)
return false;
2859 wgpu::SurfaceTexture surfTex{};
2860 surface.GetCurrentTexture(&surfTex);
2861 if (!surfTex.texture)
return false;
2862 texture = surfTex.texture;
2863 view = texture.CreateView();
2868#ifdef EVENGINE_WEBGPU
2869 if (device) device.PushErrorScope(wgpu::ErrorFilter::Validation);
2874#ifdef EVENGINE_WEBGPU
2875 if (!device)
return;
2876 device.PopErrorScope(wgpu::CallbackMode::AllowProcessEvents,
2877 [](wgpu::PopErrorScopeStatus
status, wgpu::ErrorType
type,
const char *message) {
2878 if (message &&
type != wgpu::ErrorType::NoError) {
2879 EM_ASM({ console.log(
"[GPU_ERR] type=" + $0 +
" msg=" + UTF8ToString($1)); },
2880 (int)
type, message);
2887 if (!device || !surface || !swapchainConfigured)
return;
2888 rebuildSwapchainIfNeeded();
2889 if (!swapchainConfigured)
return;
2891 wgpu::TextureView surfaceView;
2892 wgpu::Texture surfaceTex;
2893 if (!acquireSurfaceTexture(surfaceView, surfaceTex)) {
2897 auto &uboArena = currentUboArena();
2899 auto &vtxArena = currentVertexArena();
2901 voxelInstanceArena.used = 0;
2902 ensureUboArena(uboArena, 4096);
2903 ensureVertexArena(vtxArena, 4096);
2907 hasPendingClear =
false;
2909 wgpu::CommandEncoder encoder = device.CreateCommandEncoder();
2912 if (shadowDepthArray) {
2913 bool anyShadow =
false;
2915 if (!shadowCascadeDraws[
c].empty()) anyShadow =
true;
2918 if (shadowCascadeDraws[
c].empty())
continue;
2919 WGPUTextureViewDescriptor lvd{};
2920 lvd.format = WGPUTextureFormat_Depth32Float;
2921 lvd.dimension = WGPUTextureViewDimension_2D;
2922 lvd.baseMipLevel = 0;
2923 lvd.mipLevelCount = 1;
2924 lvd.baseArrayLayer =
static_cast<uint32_t
>(
c);
2925 lvd.arrayLayerCount = 1;
2926 wgpu::TextureView layerView = shadowDepthArray->
texture.CreateView(
2927 reinterpret_cast<const wgpu::TextureViewDescriptor*
>(&lvd));
2928 WGPURenderPassDepthStencilAttachment ds{};
2929 ds.view = layerView.Get();
2930 ds.depthClearValue = 1.f;
2931 ds.depthLoadOp = WGPULoadOp_Clear;
2932 ds.depthStoreOp = WGPUStoreOp_Store;
2933 ds.stencilClearValue = 0;
2934 ds.stencilLoadOp = WGPULoadOp_Undefined;
2935 ds.stencilStoreOp = WGPUStoreOp_Undefined;
2936 WGPURenderPassDescriptor rp{};
2937 rp.depthStencilAttachment = &ds;
2938 wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(
reinterpret_cast<const wgpu::RenderPassDescriptor*
>(&rp));
2939 flushShadowPass(pass);
2946 wgpu::TextureView sceneView;
2947 wgpu::Texture sceneTex;
2948 if (frameHad3DThisFrame && !sceneColorSlots.empty()) {
2949 SceneColorSlot &slot = sceneColorSlots[currentFrameSlot()];
2950 WGPURenderPassColorAttachment colorAtt{};
2951 colorAtt.view = slot.colorView.Get();
2952 colorAtt.depthSlice = WGPU_DEPTH_SLICE_UNDEFINED;
2953 colorAtt.loadOp = WGPULoadOp_Clear;
2954 colorAtt.storeOp = WGPUStoreOp_Store;
2955 colorAtt.clearValue = {clearColor.r, clearColor.g, clearColor.b, 1.f};
2956 WGPURenderPassDepthStencilAttachment ds{};
2957 ds.view = slot.depthView.Get();
2958 ds.depthClearValue = 1.f;
2959 ds.depthLoadOp = WGPULoadOp_Clear;
2960 ds.depthStoreOp = WGPUStoreOp_Store;
2961 ds.stencilClearValue = 0;
2962 ds.stencilLoadOp = WGPULoadOp_Undefined;
2963 ds.stencilStoreOp = WGPUStoreOp_Undefined;
2964 WGPURenderPassDescriptor rp{};
2965 rp.colorAttachmentCount = 1;
2966 rp.colorAttachments = &colorAtt;
2967 rp.depthStencilAttachment = &ds;
2968 wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(
reinterpret_cast<const wgpu::RenderPassDescriptor*
>(&rp));
2969 flushVoxelDraws(pass, sceneColorFormat);
2970 flushMesh3D(pass, sceneColorFormat);
2972 sceneView = slot.colorView;
2973 sceneTex = slot.color;
2977 if (gbufferPassPending && !gbufferSlots.empty()) {
2978 GbufferSlot &slot = gbufferSlots[currentFrameSlot()];
2979 WGPURenderPassColorAttachment colorAtts[3]{};
2980 for (
int i = 0; i < 3; ++i) {
2981 colorAtts[i].depthSlice = WGPU_DEPTH_SLICE_UNDEFINED;
2982 colorAtts[i].loadOp = WGPULoadOp_Clear;
2983 colorAtts[i].storeOp = WGPUStoreOp_Store;
2984 colorAtts[i].clearValue = {0.f, 0.f, 0.f, 1.f};
2986 colorAtts[0].view = slot.normalView.Get();
2987 colorAtts[1].view = slot.depthColorView.Get();
2988 colorAtts[2].view = slot.albedoView.Get();
2989 WGPURenderPassDepthStencilAttachment ds{};
2990 ds.view = slot.depthView.Get();
2991 ds.depthClearValue = 1.f;
2992 ds.depthLoadOp = WGPULoadOp_Clear;
2993 ds.depthStoreOp = WGPUStoreOp_Store;
2994 ds.stencilClearValue = 0;
2995 ds.stencilLoadOp = WGPULoadOp_Undefined;
2996 ds.stencilStoreOp = WGPUStoreOp_Undefined;
2997 WGPURenderPassDescriptor rp{};
2998 rp.colorAttachmentCount = 3;
2999 rp.colorAttachments = colorAtts;
3000 rp.depthStencilAttachment = &ds;
3001 wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(
reinterpret_cast<const wgpu::RenderPassDescriptor*
>(&rp));
3002 flushGbufferPass(pass);
3014 WGPURenderPassColorAttachment colorAtt{};
3015 colorAtt.view = surfaceView.Get();
3016 colorAtt.depthSlice = WGPU_DEPTH_SLICE_UNDEFINED;
3017 colorAtt.loadOp = WGPULoadOp_Clear;
3018 colorAtt.storeOp = WGPUStoreOp_Store;
3019 colorAtt.clearValue = {clearColor.r, clearColor.g, clearColor.b, 1.f};
3020 WGPURenderPassDescriptor rp{};
3021 rp.colorAttachmentCount = 1;
3022 rp.colorAttachments = &colorAtt;
3023 wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(
reinterpret_cast<const wgpu::RenderPassDescriptor*
>(&rp));
3025 if (sceneView && !sceneColorComposited) {
3026 if (!fullscreenQuadReady) {
3029 -1.f, -1.f, 1.f, 1.f, 1.f, 1.f, 0.f, 0.f,
3030 1.f, -1.f, 1.f, 1.f, 1.f, 1.f, 1.f, 0.f,
3031 1.f, 1.f, 1.f, 1.f, 1.f, 1.f, 1.f, 1.f,
3032 -1.f, 1.f, 1.f, 1.f, 1.f, 1.f, 0.f, 1.f,
3034 uint32_t indices[6] = {0, 1, 2, 2, 3, 0};
3035 WGPUBufferDescriptor vbd{};
3036 vbd.label = sv(
"eve_fullscreen_vb");
3037 vbd.size =
sizeof(
verts);
3038 vbd.usage = WGPUBufferUsage_CopyDst | WGPUBufferUsage_Vertex;
3039 fullscreenQuadVb = device.CreateBuffer(
reinterpret_cast<const wgpu::BufferDescriptor*
>(&vbd));
3040 queue.WriteBuffer(fullscreenQuadVb, 0,
verts,
sizeof(
verts));
3041 WGPUBufferDescriptor ibd{};
3042 ibd.label = sv(
"eve_fullscreen_ib");
3043 ibd.size =
sizeof(indices);
3044 ibd.usage = WGPUBufferUsage_CopyDst | WGPUBufferUsage_Index;
3045 fullscreenQuadIb = device.CreateBuffer(
reinterpret_cast<const wgpu::BufferDescriptor*
>(&ibd));
3046 queue.WriteBuffer(fullscreenQuadIb, 0, indices,
sizeof(indices));
3047 fullscreenQuadReady =
true;
3052 sceneGpu.
view = sceneView;
3053 sceneGpu.
sampler = createLinearSampler(device);
3054 wgpu::BindGroup bg = makeTex2DBindGroup(&sceneGpu,
nullptr);
3055 uint32_t offsets[1] = {0};
3056 pass.SetPipeline(texturedPipeline);
3057 pass.SetBindGroup(0, bg, 1, offsets);
3058 pass.SetVertexBuffer(0, fullscreenQuadVb, 0, 4 * 32);
3059 pass.SetIndexBuffer(fullscreenQuadIb, wgpu::IndexFormat::Uint32, 0, 24);
3060 pass.DrawIndexed(6, 1, 0, 0, 0);
3063 if (!activeCanvas) {
3064 flush2D(pass, pixelW > 0 ? pixelW : logicalW, pixelH > 0 ? pixelH : logicalH,
3069 WGPURenderPassEncoder cPass = pass.Get();
3075 wgpu::CommandBuffer cmd = encoder.Finish();
3076 queue.Submit(1, &cmd);
3081#ifdef __EMSCRIPTEN__
3082 if (instance) instance.ProcessEvents();
3089#if !defined(__EMSCRIPTEN__)
3094 frame3DStarted =
false;
3095 frameHad3DThisFrame =
false;
3097 sceneColorPassOpen =
false;
3098 gbufferPassPending =
false;
3107 ownedCanvases.push_back(std::unique_ptr<eve::graphics::Canvas>(
c));
3112 if (activeCanvas && canvas != activeCanvas) {
3115 activeCanvas = canvas;
3122 if (!canvas || !canvas->
getTexture())
return;
3123 auto &uboArena = currentUboArena();
3125 auto &vtxArena = currentVertexArena();
3128 wgpu::CommandEncoder enc = device.CreateCommandEncoder();
3129 WGPURenderPassColorAttachment ca{};
3130 ca.view = canvas->colorView.Get();
3131 ca.depthSlice = WGPU_DEPTH_SLICE_UNDEFINED;
3132 ca.loadOp = canvas->
clearRequested ? WGPULoadOp_Clear : WGPULoadOp_Load;
3133 ca.storeOp = WGPUStoreOp_Store;
3136 WGPURenderPassDescriptor rp{};
3137 rp.colorAttachmentCount = 1;
3138 rp.colorAttachments = &ca;
3139 wgpu::RenderPassEncoder pass = enc.BeginRenderPass(
reinterpret_cast<const wgpu::RenderPassDescriptor*
>(&rp));
3140 flush2D(pass, canvas->
getWidth(), canvas->
getHeight(), WGPUTextureFormat_RGBA8Unorm);
3142 wgpu::CommandBuffer cmd = enc.Finish();
3143 queue.Submit(1, &cmd);
3148 if (activeCanvas)
return activeCanvas->
getTexture();
3149 return sceneColorTexture;
3163 std::optional<double> ) {
3165 hasPendingClear =
true;
3170 if (!sceneColorSlots.empty()) {
3188bool copyTextureToCpu(wgpu::Instance &instance, wgpu::Device &
device, wgpu::Queue &queue,
3189 wgpu::Texture src,
int width,
int height, std::vector<uint8_t> &outRgba) {
3190 if (!src)
return false;
3191 uint64_t bytesPerRow =
static_cast<uint64_t
>(
width * 4);
3192 bytesPerRow = (bytesPerRow + 255) / 256 * 256;
3193 uint64_t size = bytesPerRow *
height;
3195 WGPUBufferDescriptor bd{};
3196 bd.label = sv(
"eve_readback");
3198 bd.usage = WGPUBufferUsage_CopyDst | WGPUBufferUsage_MapRead;
3199 bd.mappedAtCreation =
false;
3200 wgpu::Buffer dst =
device.CreateBuffer(
reinterpret_cast<const wgpu::BufferDescriptor*
>(&bd));
3202 wgpu::CommandEncoder enc =
device.CreateCommandEncoder();
3203 WGPUTexelCopyTextureInfo from{};
3204 from.texture = src.Get();
3206 from.aspect = WGPUTextureAspect_All;
3207 from.origin = {0, 0, 0};
3208 WGPUTexelCopyBufferInfo to{};
3209 to.buffer = dst.Get();
3210 to.layout.offset = 0;
3211 to.layout.bytesPerRow =
static_cast<uint32_t
>(bytesPerRow);
3212 to.layout.rowsPerImage =
static_cast<uint32_t
>(
height);
3213 WGPUExtent3D extent{
static_cast<uint32_t
>(
width),
static_cast<uint32_t
>(
height), 1};
3214 enc.CopyTextureToBuffer(
reinterpret_cast<const wgpu::TexelCopyTextureInfo*
>(&from),
3215 reinterpret_cast<const wgpu::TexelCopyBufferInfo*
>(&to),
3216 reinterpret_cast<const wgpu::Extent3D*
>(&extent));
3217 wgpu::CommandBuffer cmd = enc.Finish();
3218 queue.Submit(1, &cmd);
3220 bool mapped =
false;
3221 WGPUBufferMapCallbackInfo cbInfo{};
3222 cbInfo.mode = WGPUCallbackMode_AllowProcessEvents;
3223 cbInfo.callback = [](WGPUMapAsyncStatus
status, WGPUStringView ,
void *userdata1,
3225 bool *
ok =
static_cast<bool *
>(userdata1);
3226 *
ok = (
status == WGPUMapAsyncStatus_Success);
3228 cbInfo.userdata1 = &mapped;
3229 wgpuBufferMapAsync(dst.Get(), WGPUMapMode_Read, 0, size, cbInfo);
3232 while (!mapped && guard < 2000) {
3233#if defined(__EMSCRIPTEN__)
3234 emscripten_sleep(0);
3236 wgpuInstanceProcessEvents(instance.Get());
3239 if (!mapped)
return false;
3241 const uint8_t *
data =
static_cast<const uint8_t *
>(dst.GetConstMappedRange(0, size));
3242 if (!data)
return false;
3243 outRgba.resize(
static_cast<size_t>(
width) *
height * 4);
3245 std::memcpy(outRgba.data() + size_t(
y) *
width * 4,
data + size_t(
y) * bytesPerRow,
3254 int w = canvas ? canvas->
getWidth() : (sceneColorWidth > 0 ? sceneColorWidth : 1);
3255 int h = canvas ? canvas->
getHeight() : (sceneColorHeight > 0 ? sceneColorHeight : 1);
3256 if (
x < 0 || y < 0 || x >=
w ||
y >=
h)
return Color(0.f, 0.f, 0.f, 0.f);
3257 std::vector<uint8_t> rgba;
3258 wgpu::Texture src = canvas ? canvas->color : (sceneColorSlots.empty() ? nullptr : sceneColorSlots[0].color);
3259 if (!src || !copyTextureToCpu(instance,
device, queue, src,
w,
h, rgba))
return clearColor;
3260 const uint8_t *
p = rgba.data() + (size_t(
y) *
w +
x) * 4;
3261 return Color(
p[0] / 255.f,
p[1] / 255.f,
p[2] / 255.f,
p[3] / 255.f);
3265 int w = canvas ? canvas->
getWidth() : (sceneColorWidth > 0 ? sceneColorWidth : 1);
3266 int h = canvas ? canvas->
getHeight() : (sceneColorHeight > 0 ? sceneColorHeight : 1);
3268 std::vector<uint8_t> rgba;
3269 wgpu::Texture src = canvas ? canvas->color : (sceneColorSlots.empty() ? nullptr : sceneColorSlots[0].color);
3270 if (src && copyTextureToCpu(instance,
device, queue, src,
w,
h, rgba)) {
3271 std::memcpy(img->getData(), rgba.data(), rgba.size());
3282wgpu::RenderPipeline buildPipelineFromWgsl(wgpu::Device &dev, wgpu::PipelineLayout
layout,
3283 WGPUTextureFormat format,
const std::string &
vert,
3284 const std::string &
frag,
bool depth,
bool blend,
3285 bool mesh3d,
bool hair,
bool shadow,
bool gbuffer,
3286 uint32_t sampleCount) {
3287 WGPURenderPipelineDescriptor pd{};
3288 pd.label = sv(
"eve_custom_shader");
3289 pd.layout =
layout.Get();
3291 WGPUVertexAttribute attrs[3]{};
3292 WGPUVertexBufferLayout vb{};
3293 if (mesh3d || shadow || gbuffer) {
3294 attrs[0].format = WGPUVertexFormat_Float32x3;
3295 attrs[0].offset = 0;
3296 attrs[0].shaderLocation = 0;
3297 attrs[1].format = WGPUVertexFormat_Float32x3;
3298 attrs[1].offset = 12;
3299 attrs[1].shaderLocation = 1;
3300 attrs[2].format = WGPUVertexFormat_Float32x2;
3301 attrs[2].offset = 24;
3302 attrs[2].shaderLocation = 2;
3303 vb.arrayStride = 32;
3304 vb.stepMode = WGPUVertexStepMode_Vertex;
3305 vb.attributeCount = 3;
3306 vb.attributes = attrs;
3308 attrs[0].format = WGPUVertexFormat_Float32x2;
3309 attrs[0].offset = 0;
3310 attrs[0].shaderLocation = 0;
3311 attrs[1].format = WGPUVertexFormat_Float32x4;
3312 attrs[1].offset = 8;
3313 attrs[1].shaderLocation = 1;
3314 attrs[2].format = WGPUVertexFormat_Float32x2;
3315 attrs[2].offset = 24;
3316 attrs[2].shaderLocation = 2;
3317 vb.arrayStride = 32;
3318 vb.stepMode = WGPUVertexStepMode_Vertex;
3319 vb.attributeCount = 3;
3320 vb.attributes = attrs;
3322 pd.vertex.bufferCount = 1;
3323 pd.vertex.buffers = &vb;
3325 if (!
vert.empty()) {
3326 WGPUShaderModuleDescriptor md = mdDesc(
vert);
3327 wgpu::ShaderModule
vm = dev.CreateShaderModule(
3328 reinterpret_cast<const wgpu::ShaderModuleDescriptor*
>(&md));
3329 pd.vertex.module =
vm.Get();
3330 pd.vertex.entryPoint = sv(
"vs_main");
3334 wgpu::ShaderModule
vm = dev.CreateShaderModule(
3335 reinterpret_cast<const wgpu::ShaderModuleDescriptor*
>(&md));
3336 pd.vertex.module =
vm.Get();
3337 pd.vertex.entryPoint = sv(
"vs_main");
3340 if (!
frag.empty()) {
3341 WGPUFragmentState fs{};
3342 WGPUShaderModuleDescriptor md = mdDesc(
frag);
3343 wgpu::ShaderModule fm = dev.CreateShaderModule(
3344 reinterpret_cast<const wgpu::ShaderModuleDescriptor*
>(&md));
3345 fs.module = fm.Get();
3346 fs.entryPoint = sv(
"fs_main");
3348 WGPUColorTargetState target{};
3349 target.format = format;
3350 target.writeMask = WGPUColorWriteMask_All;
3352 static WGPUBlendState bs = alphaBlend();
3355 fs.targets = ⌖
3358 pd.fragment =
nullptr;
3361 pd.primitive.topology = WGPUPrimitiveTopology_TriangleList;
3362 pd.primitive.frontFace = WGPUFrontFace_CCW;
3363 pd.primitive.cullMode =
hair ? WGPUCullMode_None : WGPUCullMode_None;
3364 pd.primitive.stripIndexFormat = WGPUIndexFormat_Undefined;
3366 if (
depth && !shadow) {
3367 static WGPUDepthStencilState ds{};
3368 ds.format = WGPUTextureFormat_Depth32Float;
3369 ds.depthWriteEnabled = WGPUOptionalBool_True;
3370 ds.depthCompare = WGPUCompareFunction_Less;
3371 pd.depthStencil = &ds;
3373 pd.multisample.count = sampleCount ? sampleCount : 1;
3375 pd.multisample.mask = 0xFFFFFFFFu;
3376 return dev.CreateRenderPipeline(
reinterpret_cast<const wgpu::RenderPipelineDescriptor*
>(&pd));
3382 const std::vector<uint32_t> &fragSpv) {
3383 if (!
device)
throw Exception(
"newShaderFromSpv: device not initialized");
3387 throw Exception(
"newShaderFromSpv: SPIR-V custom shaders are not supported on the "
3388 "WebGPU backend (browsers accept WGSL only). Recompile the shader "
3389 "with the WebGPU toolchain (glslc+tint) and use newShaderFromWgsl.");
3393 throw Exception(
"newShaderFromSpvFile: SPIR-V custom shaders are not supported on the "
3394 "WebGPU backend. Use WGSL shaders instead.");
3401 auto gpuIt = std::find_if(ownedGpuShaders.begin(), ownedGpuShaders.end(),
3402 [&](
const std::unique_ptr<GpuShader> &g) {
3403 return g.get() == gpu;
3405 if (gpuIt == ownedGpuShaders.end())
return false;
3407 auto shIt = std::find_if(ownedShaders.begin(), ownedShaders.end(),
3408 [&](
const std::unique_ptr<Shader> &
s) {
3409 return s.get() == shader;
3411 if (shIt == ownedShaders.end())
return false;
3413 shader->gpuHandle =
nullptr;
3414 ownedGpuShaders.erase(gpuIt);
3416 (void)shIt->release();
3417 ownedShaders.erase(shIt);
3424 throw Exception(
"newShader: runtime GLSL compilation is not available on the WebGPU "
3425 "backend (browser WGSL only). Ship pre-compiled WGSL shaders.");
3429 const std::vector<uint32_t> &fragSpv) {
3432 throw Exception(
"newMeshShaderFromSpv: SPIR-V custom mesh shaders are not supported on the "
3433 "WebGPU backend. Use WGSL shaders instead.");
3437 if (!
device)
throw Exception(
"newMeshShaderFromWgsl: device not initialized");
3438 if (fragWgsl.empty())
throw Exception(
"newMeshShaderFromWgsl: empty fragment WGSL");
3439 if (!mesh3dSetLayout)
throw Exception(
"newMeshShaderFromWgsl: mesh3d layout missing");
3443 auto gpu = std::make_unique<GpuShader>();
3444 gpu->isMesh3D =
true;
3445 gpu->wgslVert =
vert;
3446 gpu->wgslFrag = fragWgsl;
3447 gpu->mesh3dPipeline =
3448 buildPipelineFromWgsl(
device, mesh3dPipelineLayout, sceneColorFormat,
vert, fragWgsl,
3449 true,
false,
true,
false,
3453 gpu->mesh3dXrayPipeline =
3454 buildPipelineFromWgsl(
device, mesh3dPipelineLayout, sceneColorFormat,
vert, fragWgsl,
3455 false,
true,
true,
false,
3458 auto sh = std::make_unique<Shader>();
3460 sh->gpuHandle = gpu.get();
3463 ownedShaders.push_back(std::move(sh));
3464 ownedGpuShaders.push_back(std::move(gpu));
3471 throw Exception(
"newMeshShader: runtime GLSL compilation is not available on the WebGPU "
3472 "backend. Ship pre-compiled WGSL shaders.");
3476 const std::vector<uint32_t> &fragSpv) {
3479 throw Exception(
"newHairShaderFromSpv: SPIR-V custom hair shaders are not supported on the "
3480 "WebGPU backend. Use WGSL shaders instead.");
3490 "newFont: fonts are not supported on the WebGPU backend (font module is not "
3491 "part of the WASM build)");
3494void Graphics::print(
const std::string &,
float,
float,
const Color &,
float) {
3496 "print: fonts are not supported on the WebGPU backend (font module is not part "
3497 "of the WASM build)");
std::vector< std::uint32_t > verts
image::ImageData::Colorf color
CPU-side decoded font face (FreeType FT_Face + owned font bytes). Does not upload to GPU — rasterize ...
Accumulates solid / textured quads in logical (Y-down) coordinates. Used by RenderSystem; not a publi...
virtual Texture * getTexture()=0
Sampleable color buffer; screen Canvas returns nullptr.
GPU-side font: wraps a decoded font::FontData and rasterizes a fixed set of codepoints into a single ...
virtual void setVSync(bool enabled)
Prefer uncapped present (IMMEDIATE/MAILBOX) when false, vsync (MAILBOX/FIFO) when true....
void * presentOverlayUser_
std::unique_ptr< RenderControl > renderControl_
PresentOverlayFn presentOverlayFn_
GPU mesh handle (+ optional CPU morph targets).
static constexpr uint32_t kPushConstantBytes
GPU texture created via Graphics::newTexture. Owns GPU resources through an opaque backend handle.
void drawMeshShadowAlpha(Mesh *mesh, const glm::mat4 &lightMVP, Texture *albedo=nullptr) override
Shadow pass draw with alpha-cutout discard (card/billboard geometry such as sprite-stack slices): tra...
void pushValidationScope() override
Backend hooks so the platform-independent render3D() can wrap its work in a GPU validation error scop...
Texture * newCubemap(int faceSize, const uint8_t *rgbaFaces) override
Create an RGBA8 cubemap from 6 faces packed as +X,-X,+Y,-Y,+Z,-Z (each faceSize×faceSize,...
void setMesh3DEnv(Texture *cube, float intensity) override
Specular IBL environment for subsequent default mesh draws. cube must be from newCubemap (or nullptr ...
void setMesh3DLight(const glm::vec3 &dir, const glm::vec3 &color) override
Directional light for subsequent drawMesh calls (world-space direction toward surface).
void drawMeshShadow(Mesh *mesh, const glm::mat4 &lightMVP) override
Shader * newMeshShader(const std::string &vertGlsl, const std::string &fragGlsl) override
void flush2DToCanvas(OffscreenCanvas *canvas)
Flush accumulated 2D batches into an offscreen canvas target.
void setMesh3DViewProj(const glm::mat4 &viewProj) override
void draw(eve::graphics::Graphics *gfx, const glm::mat4 &matrix) const override
Draws the object with the specified transformation matrix.
void setMesh3DShadowReceive(bool receive) override
Per-draw: when false, shadow sampling is forced off for the next mesh draw.
Shader * newShaderFromSpvFile(const std::string &vertPath, const std::string &fragPath) override
Load SPIR-V from files via Filesystem (empty vertPath → default textured vert).
float getMaxAnisotropy() const override
Device max supported anisotropy (1 if unsupported). Valid after initWithWindow.
Mesh * newMeshSphere(int slices=32, int stacks=16) override
Procedural UV sphere (radius 1, Y-up). Owned by Graphics. slices = longitude divisions,...
Color getPixelImpl(OffscreenCanvas *canvas, int x, int y)
Blocking CPU readback of an offscreen canvas or scene color target.
void popValidationScope() override
void clear(std::optional< Color > color, std::optional< int > stencil, std::optional< double > depth) override
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) override
GBuffer fill with alpha-cutout discard (card/billboard geometry such as sprite-stack slices): same ou...
void setMesh3DNormalTexture(Texture *normal) override
Optional normal map for the next drawMesh / drawMeshShader (nullptr = flat).
void setMesh3DHeightTexture(Texture *height) override
Optional height map for parallax (R channel; nullptr = flat / off).
void begin3DFrame() override
Begin a 3D frame: shadow/gbuffer (if pending) then a sampleable scene color pass (color+depth)....
Mesh * newMeshFromAssimp(const ::aiMesh &mesh) override
void setMesh3DCameraPos(const glm::vec3 &eye) override
Camera eye used by mesh shaders that need view/rim (stored in Mesh3DUBO).
void beginGBufferPass(int width, int height) override
Depth/normal(/albedo) fill pass for mid/post effects. One-shot submit (like shadow); call before begi...
Shader * newMeshShaderFromWgsl(const std::string &vertWgsl, const std::string &fragWgsl) override
Create a Mesh3D custom shader from WGSL source (WebGPU backend). The WGSL must declare the engine's F...
bool bakeMeshMorph(Mesh *mesh) override
If mesh morph weights are dirty, bake blended positions and upload to the GPU VBO....
Texture * newTextureFromFile(const std::string &filename) override
void setViewportSize(int width, int height, int pixelwidth, int pixelheight) override
Sets the current graphics display viewport dimensions.
void setMesh3DLighting(const Lighting3DPack &pack) override
Per-frame ambient + up to 8 lights packed into Mesh3DUBO.
image::ImageData * newImageData() override
void drawMeshShader(Mesh *mesh, const glm::mat4 &model, Texture *texture, const Color &tint, Shader *shader) override
Draw mesh with an explicit Mesh3D Shader (nullptr = default PBR pipeline).
void setMesh3DMaterial(float metallic, float roughness) override
Metallic (0..1) and roughness (0..1) for the next default mesh draw.
void drawSolidRectRotated(float cx, float cy, float w, float h, float degrees, const Color &color, BlendMode blend=BlendMode::Alpha) override
Rotated solid quad degrees clockwise (screen Y-down) around (cx, cy).
void setLighting2D(const Lighting2DUBO &ubo) override
Upload per-frame / per-canvas 2D lighting constants for subsequent lit draws.
void endGBufferPass() override
void setMesh3DShadows(const ShadowUpload &upload) override
Upload CSM constants for subsequent default mesh draws (active=false disables).
friend class OffscreenCanvas
Shader * newMeshShaderFromSpv(const std::vector< uint32_t > &vertSpv, const std::vector< uint32_t > &fragSpv) override
Create a Mesh3D custom shader (MeshVertex + Frame UBO + albedo). Empty vert → default mesh3d....
Texture * newTexture(int width, int height, const uint8_t *rgba, bool repeatU=false, bool repeatV=false) override
void end3DFrameToCanvas() override
bool updateMeshVertices(Mesh *mesh, const float *posXYZ, const float *nrmXYZ, const float *uvST, int vertexCount, const uint32_t *indices, int indexCount) override
In-place update of a mesh's vertex/index data (CPU -> host-visible VBO). Mirrors bakeMeshMorph: the u...
void drawMesh(Mesh *mesh, const glm::mat4 &model, Texture *texture, const Color &tint) override
Draw one mesh with model matrix. Requires begin3DFrame() (or an open swapchain pass).
Shader * newHairShaderFromSpv(const std::vector< uint32_t > &vertSpv, const std::vector< uint32_t > &fragSpv) override
Hair/fur card shader (alpha blend + Kajiya-Kay). Empty vert → mesh3d_hair.vert. Owned by Graphics.
Shader * newShaderFromSpv(const std::vector< uint32_t > &vertSpv, const std::vector< uint32_t > &fragSpv) override
Create a custom 2D shader from SPIR-V words (vert + frag). Owned by Graphics. Vertex stage may be emp...
void setMesh3DView(const glm::mat4 &view) override
Camera view matrix for subsequent drawMesh (view-space depth / CSM select).
void drawTexturedRect(Texture *texture, float x, float y, float w, float h, const Color &color) override
void begin3DFrameToCanvas(Canvas *canvas) override
Open a 3D render pass targeting an offscreen Canvas (color + depth) at the canvas size....
void drawTexturedRectUV(Texture *texture, float x, float y, float w, float h, float u0, float v0, float u1, float v1, const Color &color) override
Draw a textured sub-rect (atlas / tile UVs). texture may be null → solid.
bool releaseShader(Shader *shader) override
Eagerly releases a shader created by this Graphics.
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) override
void setMesh3DClusteredLighting(const ClusteredLightingUpload &upload) override
Enable clustered forward path for subsequent default mesh draws (SSBO light lists)....
void setMesh3DTexCellBomb(float cellScale, float strength, float rotAmount=1.f) override
Texture cell bombing for the next default mesh draw (breaks tiling). cellScale: cells per UV unit (ty...
void initWithWindow(void *nativeWindow) override
Bind to an existing native window (SDL_Window*) and create Vulkan device/swapchain....
Mesh * newMeshCylinder(int slices=32, int stacks=1, bool caps=true) override
Procedural Y-up cylinder (radius 1, height 2 centered at origin). slices = longitude divisions; stack...
image::ImageData * newImageDataImpl(OffscreenCanvas *canvas)
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
UV draw rotated degrees clockwise (screen Y-down) around the rect center. texture may be null → solid...
void setMesh3DParallax(float scale, float minLayers=8.f, float maxLayers=32.f) override
Parallax occlusion mapping for the next default mesh draw. scale: UV displacement strength (0=off)....
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) override
Lit 2D draw (albedo + normal map). Uses Lighting2DUBO from setLighting2D. normal may be null → treate...
Mesh * newMeshFromArrays(const float *posXYZ, const float *nrmXYZ, const float *uvST, int vertexCount, const uint32_t *indices, int indexCount) override
Upload a triangle mesh from packed CPU arrays. Owned by Graphics. posXYZ required (vertexCount*3)....
Shader * newShader(const std::string &vertGlsl, const std::string &fragGlsl) override
Compile GLSL source with glslc (must be on PATH). Empty vertGlsl → default textured vert....
void setMesh3DClusteredActive(bool active) override
Cheap per-draw toggle for the already-uploaded clustered light table. Unlike setMesh3DClusteredLighti...
bool reloadTextureFromFile(const std::string &filename) override
Reload a path-cached texture from disk in place (pointer stable). False if unbound.
void setCloudShadows(float strength, float worldCell, float time, float windSpeed, float windAngle, float coverage, float detail) override
Dynamic cloud shadows cast on the ground by the default PBR mesh path. strength 0 disables (no change...
void setTextureSampler(Texture *texture, const TextureSampler &sampler) override
Recreate the sampler for an existing texture (keeps image / mip chain). No-op when texture is null or...
bool releaseMesh(Mesh *mesh) override
Eagerly releases a mesh created by this Graphics.
void setMesh3DClip(float nearZ, float farZ) override
Near/far used to pack linear depth into scene color A (SSGI).
void endShadowPass() override
void setMesh3DSceneDepth(Texture *depth) override
Optional scene hardware depth (G-buffer hwDepth, Vulkan NDC z) bound to mesh3d shader binding 7....
bool releaseTexture(Texture *texture) override
Eagerly releases a texture created by this Graphics.
Color getPixel(int x, int y) override
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
UV draw with an explicit Shader (nullptr = default textured pipeline).
Texture * getTexture() override
Sampleable color buffer; screen Canvas returns nullptr.
void drawSolidRect(float x, float y, float w, float h, const Color &color, BlendMode blend=BlendMode::Alpha) override
Internal immediate-mode helper used by RenderSystem / Batcher.
void drawVoxelFaceInstances(const uint32_t *packed, int count, float originX, float originY, float originZ, const std::string &faceDir, Texture *atlas, int tilesPerRow=16, const uint32_t *ao=nullptr) override
Instanced voxel face rectangles (32-bit packed instances). ao: optional per-instance ambient-occlusio...
Canvas * newCanvas(int width, int height) override
Create an offscreen render target (sampleable). Owned by Graphics.
void drawTexturedRectShader(Texture *texture, Shader *shader, float x, float y, float w, float h, const Color &color) override
Draw with an explicit Shader (nullptr = default textured pipeline).
void drawTexturedRectShaderDepth(Texture *color, Texture *depth, Shader *shader, float x, float y, float w, float h, const Color &tint) override
Fullscreen/post draw sampling color at binding 0 and depth at binding 1 (hardware D32,...
Canvas * getCanvas() const override
void setVSync(bool enabled) override
Prefer uncapped present (IMMEDIATE/MAILBOX) when false, vsync (MAILBOX/FIFO) when true....
bool isCanvasActive() const override
void beginShadowPass(int cascadeIndex) override
Depth-only shadow pass for one cascade layer (0..2). Draws are recorded into the next begin3DFrame co...
Texture * getSceneColorTexture() override
Sampleable 3D color target for the current frame (RGB = lit, A = linear depth). Valid after begin3DFr...
Offscreen render target (RGBA8Unorm color, optional Depth32Float). 2D batches are flushed into the ca...
int getWidth() const override
int getHeight() const override
Texture * getTexture() override
Sampleable color buffer; screen Canvas returns nullptr.
Represents raw pixel data.
ref is a smart pointer that automatically calls ref() and unref() on the object. Type T must be a sub...
const char * kColorVertWgsl
const char * kMesh3DFragWgsl
const char * kColorFragWgsl
const char * kTexturedVertWgsl
const char * kMesh3DVertWgsl
const char * kMesh3DGbufferFragWgsl
const char * kMesh3DShadowVertWgsl
const char * kTexturedFragWgsl
const char * kLit2DFragWgsl
const char * kVoxelRectVertWgsl
const char * kVoxelRectFragWgsl
const char * kMesh3DGbufferVertWgsl
卡牌游戏 UI 工具模块:工厂 + 脚本绑定入口。 功能参考 ycarowr/UiCard:扇形手牌布局、抽牌/洗牌、悬浮放大、拖拽到落牌区、 敌方手牌(背面/偷看)、费用不足置灰,以及可实时调节的布局...
int mipmapCountForSize(int width, int height)
Full mip chain count for a 2D image (including base level).
glm::vec4 Color
RGBA color used by every graphics draw call. Lives inside eve::graphics so including a graphics heade...
BlendMode
2D quad blend mode (drawn in draw order within a layer).
CPU-built clustered lighting upload for one frame/camera. Point lights are clustered; directional lig...
static constexpr int kMaxLights
Light3DGpu lights[kMaxLights]
static constexpr int kMapSize
static constexpr int kCascades
Options for Graphics::newTexture / newCubemap. When generateMipmaps is true and sampler....
Sampler state for a Texture (filter, wrap, mip LOD, anisotropy). Defaults match historical engine beh...
static TextureSampler linearMipmap()
Trilinear (linear + linear mips). Caller should create the texture with generateMipmaps.
Vertex/index buffers for one mesh.
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.