16using SamplerKey = std::array<std::uint32_t, 4>;
18wgpu::AddressMode addressMode(std::uint32_t
value) {
19 return value == 33071 ? wgpu::AddressMode::ClampToEdge
20 :
value == 33648 ? wgpu::AddressMode::MirrorRepeat
21 : wgpu::AddressMode::Repeat;
24wgpu::Sampler makePbrSampler(
const wgpu::Device&
device,
const PbrTextureBinding&
binding) {
25 const auto nearestMin =
binding.minFilter == 9728 ||
binding.minFilter == 9984 ||
binding.minFilter == 9986;
26 return SamplerBuilder()
27 .address(addressMode(
binding.wrapS), addressMode(
binding.wrapT), wgpu::AddressMode::Repeat)
31 0.f,
binding.minFilter >= 9984 ? 1000.f : 0.f)
36 return std::size_t(
blend) * 16u + (depthWrite ? 8u : 0
u) + (alphaToCoverage ? 4u : 0
u) +
44 WGPUTextureFormat
format = WGPUTextureFormat_Undefined;
58 mesh3dPbrSurface.reset();
69 const auto requireKind = [&](
Texture* texture,
const char* role,
bool array,
bool volume,
72 auto* gpu = gpuForTexture(texture);
73 if (!gpu || gpu->isCube || gpu->isVolume !=
volume || (!
volume && gpu->isArray != array) ||
74 (array && std::uint32_t(texture->
layers) < minimumLayers))
80 auto ready = requireGpu(
binding.texture,
"PBR texture");
81 if (!ready)
return ready;
82 ready = requireKind(
binding.texture,
"PBR texture",
false,
false);
83 if (!ready)
return ready;
86 auto ready = requireGpu(
binding.texture,
"PBR detail texture");
87 if (!ready)
return ready;
88 ready = requireKind(
binding.texture,
"PBR detail texture",
false,
false);
89 if (!ready)
return ready;
91 for (
auto [texture, role] : std::array<std::pair<Texture*, const char*>, 6>{
98 auto ready = requireGpu(texture, role);
99 if (!ready)
return ready;
101 for (
auto [texture, role,
layer] :
102 std::array<std::tuple<Texture*, const char*, std::uint32_t>, 4>{
107 auto ready = requireKind(texture, role,
true,
false,
layer + 1u);
108 if (!ready)
return ready;
111 if (!ready)
return ready;
113 if (!ready)
return ready;
118 mesh3dPbrSurface = *surface;
122bool Graphics::drawPbrMesh(wgpu::RenderPassEncoder pass, WGPUTextureFormat format,
bool canvasTarget,
124 const auto& surface = *
draw.pbrSurface;
125 const bool environment =
draw.environment && gpuForTexture(
draw.environment) &&
127 const bool shadows =
draw.shadows.active &&
draw.shadowReceive && shadowDepthArray;
131 if (!planned)
throw Exception(
"%s", planned.error()->message().c_str());
132 const auto& plan = planned.value();
134 if (!pbrResources_) pbrResources_.reset(
new PbrResources());
138 const bool depthWrite = !transparent ||
draw.depthWrite;
143 const std::uint32_t samples = canvasTarget ? 1u : sceneColorSamples;
144 const auto raster = rasterKey(
blend, depthWrite, cull, alphaToCoverage);
146 return candidate.plan == plan && candidate.format == format && candidate.samples == samples &&
147 candidate.raster == raster;
151 wgpu::ShaderSourceWGSL vertexWgsl{};
152 vertexWgsl.code =
source.vertex.c_str();
153 wgpu::ShaderModuleDescriptor vertexDescriptor{};
154 vertexDescriptor.nextInChain = &vertexWgsl;
155 auto vertex =
device.CreateShaderModule(&vertexDescriptor);
156 wgpu::ShaderSourceWGSL fragmentWgsl{};
157 fragmentWgsl.code =
source.fragment.c_str();
158 wgpu::ShaderModuleDescriptor fragmentDescriptor{};
159 fragmentDescriptor.nextInChain = &fragmentWgsl;
160 auto fragment =
device.CreateShaderModule(&fragmentDescriptor);
162 std::array<wgpu::VertexAttribute, 16> attributes{};
163 auto attribute = [&](std::size_t
index, wgpu::VertexFormat vertexFormat, std::uint64_t
offset,
164 std::uint32_t location) {
165 attributes[
index].format = vertexFormat;
167 attributes[
index].shaderLocation = location;
169 attribute(0, wgpu::VertexFormat::Float32x3, 0, 0);
170 attribute(1, wgpu::VertexFormat::Float32x3, 12, 1);
171 attribute(2, wgpu::VertexFormat::Float32x2, 24, 2);
172 attribute(3, wgpu::VertexFormat::Uint16x4, 32, 3);
173 attribute(4, wgpu::VertexFormat::Float32x4, 40, 4);
175 attribute(
index + 5, wgpu::VertexFormat::Float32x2, std::uint64_t(
index) * 8u,
index + 5);
176 std::array<wgpu::VertexBufferLayout, 2> buffers{};
177 buffers[0].arrayStride =
sizeof(MeshVertex);
178 buffers[0].attributeCount = 5;
179 buffers[0].attributes = attributes.data();
180 buffers[1].arrayStride = 11u * 2u *
sizeof(float);
181 buffers[1].attributeCount = 11;
182 buffers[1].attributes = attributes.data() + 5;
184 wgpu::BlendState blendState{};
185 blendState.color.operation = wgpu::BlendOperation::Add;
186 blendState.alpha.operation = wgpu::BlendOperation::Add;
189 : wgpu::BlendFactor::SrcAlpha;
191 : wgpu::BlendFactor::OneMinusSrcAlpha;
192 blendState.alpha.srcFactor = wgpu::BlendFactor::One;
193 blendState.alpha.dstFactor = wgpu::BlendFactor::OneMinusSrcAlpha;
195 blendState.color.srcFactor = wgpu::BlendFactor::Dst;
196 blendState.color.dstFactor = wgpu::BlendFactor::Zero;
198 wgpu::ColorTargetState
target{};
199 target.format = wgpu::TextureFormat(format);
201 target.writeMask = wgpu::ColorWriteMask::All;
202 wgpu::FragmentState fragmentState{};
203 fragmentState.module = fragment;
204 fragmentState.entryPoint =
"main";
205 fragmentState.targetCount = 1;
206 fragmentState.targets = &
target;
207 wgpu::DepthStencilState
depth{};
208 depth.format = wgpu::TextureFormat::Depth32Float;
209 depth.depthWriteEnabled = depthWrite ? wgpu::OptionalBool::True : wgpu::OptionalBool::False;
210 depth.depthCompare = wgpu::CompareFunction::Less;
214 descriptor.vertex.bufferCount = buffers.size();
217 descriptor.primitive.topology = wgpu::PrimitiveTopology::TriangleList;
218 descriptor.primitive.frontFace = wgpu::FrontFace::CCW;
221 : wgpu::CullMode::Back;
225 descriptor.multisample.alphaToCoverageEnabled = alphaToCoverage;
226 PbrResources::Pipeline candidate{plan, format, samples, raster};
228 candidate.layout = candidate.pipeline.GetBindGroupLayout(0);
229 resources.pipelines.push_back(std::move(candidate));
233 float surfaceCode = float(
int(
draw.surfaceMode));
236 const std::uint32_t skinCount =
draw.mesh->hasGpuSkinning() ?
draw.mesh->getSkinPaletteCount() : 0;
240 draw.lighting,
draw.metallic,
draw.roughness, surfaceCode,
draw.alphaCutoff,
241 environment ?
draw.environmentIntensity : 0.f, skinCount});
245 for (std::size_t slot = 0; slot < surface.textures.size(); ++slot) {
246 const auto&
binding = surface.textures[slot];
249 throw Exception(
"PBR texture references missing mesh UV channel");
250 if (!
values.empty()) uniform.uvInfo[slot].offset = 0
u;
251 for (std::size_t vertexIndex = 0; vertexIndex <
vertexCount; ++vertexIndex) {
252 const auto fallback =
mesh.cpuVertices[vertexIndex].uv;
253 uv[vertexIndex * 22u + slot * 2u] =
values.empty() ? fallback.x :
values[vertexIndex * 2u];
254 uv[vertexIndex * 22u + slot * 2u + 1u] =
values.empty() ? fallback.y :
values[vertexIndex * 2u + 1u];
258 std::vector<float> attributes(2u, 0.f);
260 uniform.tangentInfo = {std::uint32_t(attributes.size()), 1u, 0
u, 0
u};
261 const auto& tangents =
draw.mesh->importedTangents();
262 const auto& bitangents =
draw.mesh->importedBitangents();
264 attributes.insert(attributes.end(), tangents.begin() +
index, tangents.begin() +
index + 3u);
265 attributes.insert(attributes.end(), bitangents.begin() +
index, bitangents.begin() +
index + 3u);
268 const auto highlights =
draw.mesh->motionHighlights();
269 if (!highlights.empty()) {
270 uniform.tangentInfo.z = std::uint32_t(attributes.size());
271 uniform.tangentInfo.w = 1u;
272 attributes.insert(attributes.end(), highlights.begin(), highlights.end());
274 const auto factors =
draw.mesh->vegetationFactors();
275 if (!factors.empty()) {
276 uniform.vegetationInfo.x = std::uint32_t(attributes.size());
277 uniform.vegetationInfo.y = 1u;
278 attributes.insert(attributes.end(), factors.begin(), factors.end());
280 const auto deformation =
draw.mesh->vegetationDeformationFactors();
281 if (!deformation.empty()) {
282 uniform.vertexInfo.z = std::uint32_t(attributes.size());
283 uniform.vertexInfo.w = 1u;
284 attributes.insert(attributes.end(), deformation.begin(), deformation.end());
287 throw Exception(
"PBR GPU vegetation motion requires a nine-float rest-deformation stream");
289 auto& arena = currentUboArena();
290 const auto uniformOffset = arena.alloc(
sizeof(uniform), 256);
291 const auto shadowOffset = arena.alloc(
sizeof(ShadowUBO), 256);
292 queue.WriteBuffer(arena.buffer, uniformOffset, &uniform,
sizeof(uniform));
293 auto shadow =
draw.shadows.ubo;
296 shadow.splits.w = 0.f;
298 shadow.bias.z =
draw.shadowReceive ? 1.f : 0.f;
299 queue.WriteBuffer(arena.buffer, shadowOffset, &shadow,
sizeof(shadow));
301 const auto allocateAux = [&](
const void* data, std::uint64_t
bytes) {
302 const auto index = arena.paletteIndex++;
303 if (arena.palettes.size() <=
index) arena.palettes.resize(
index + 1u);
308 descriptor.usage = wgpu::BufferUsage::CopyDst | wgpu::BufferUsage::Storage | wgpu::BufferUsage::Vertex;
314 auto uvBuffer = allocateAux(
uv.data(),
uv.size() *
sizeof(
float));
315 auto attributeBuffer = allocateAux(attributes.data(), attributes.size() *
sizeof(
float));
316 auto skinBuffer = uploadSkinPalette(
draw.mesh);
318 std::vector<wgpu::BindGroupEntry>
entries;
319 const auto addBuffer = [&](std::uint32_t
binding,
const wgpu::Buffer&
buffer, std::uint64_t
offset,
320 std::uint64_t
size) {
321 wgpu::BindGroupEntry entry{};
328 const auto addTexture = [&](std::uint32_t
binding, GpuTexture* texture) {
329 wgpu::BindGroupEntry entry{};
331 entry.textureView = texture->view;
334 const auto addSampler = [&](std::uint32_t
binding,
const wgpu::Sampler&
sampler) {
335 wgpu::BindGroupEntry entry{};
340 addBuffer(0, arena.buffer, uniformOffset,
sizeof(uniform));
341 addBuffer(2, attributeBuffer, 0, attributes.size() *
sizeof(
float));
342 addBuffer(3, skinBuffer, 0, skinBuffer.GetSize());
343 addBuffer(6, arena.buffer, shadowOffset,
sizeof(shadow));
345 const auto samplerFor = [&](
const PbrTextureBinding&
binding) ->
const wgpu::Sampler& {
352 for (std::uint32_t slot = 0; slot < std::uint32_t(surface.textures.size()); ++slot) {
353 if ((plan.canonicalTextureMask & (1u << slot)) == 0
u)
continue;
354 addTexture(20u + slot * 2u, gpuForTexture(surface.textures[slot].texture));
355 if (plan.canonicalSamplerRepresentatives[slot] == slot)
356 addSampler(21u + slot * 2u, samplerFor(surface.textures[slot]));
358 for (std::uint32_t slot = 0; slot < std::uint32_t(surface.vegetationDetail.textures.size()); ++slot) {
359 if ((plan.detailTextureMask & (1u << slot)) == 0
u)
continue;
360 addTexture(42u + slot * 2u, gpuForTexture(surface.vegetationDetail.textures[slot].texture));
362 addSampler(43u + slot * 2u, samplerFor(surface.vegetationDetail.textures[slot]));
365 auto* texture = gpuForTexture(
draw.environment);
366 addTexture(4, texture);
367 addSampler(5, texture->sampler);
370 addTexture(7, shadowDepthArray);
371 addSampler(8, shadowDepthArray->
sampler);
374 auto* texture = gpuForTexture(surface.vegetationExtras.texture);
375 addTexture(9, texture);
376 addSampler(10, texture->sampler);
379 auto* texture = gpuForTexture(surface.vegetationColors.texture);
380 addTexture(11, texture);
381 addSampler(12, texture->sampler);
384 auto* texture = gpuForTexture(surface.vegetationAlpha.noise);
385 addTexture(13, texture);
386 addSampler(14, texture->sampler);
389 auto* texture = gpuForTexture(surface.vegetationVertex.texture);
390 addTexture(50, texture);
391 addSampler(51, texture->sampler);
394 auto* texture = gpuForTexture(surface.vegetationMotion.texture);
395 addTexture(52, texture);
396 addSampler(53, texture->sampler);
399 auto* texture = gpuForTexture(surface.vegetationMotion.noise);
400 addTexture(54, texture);
401 addSampler(55, texture->sampler);
404 wgpu::BindGroupDescriptor bindDescriptor{};
405 bindDescriptor.layout =
found->layout;
406 bindDescriptor.entryCount =
entries.size();
407 bindDescriptor.entries =
entries.data();
408 auto bindGroup =
device.CreateBindGroup(&bindDescriptor);
409 pass.SetPipeline(
found->pipeline);
410 pass.SetBindGroup(0, bindGroup);
411 pass.SetVertexBuffer(0,
mesh.vertexBuffer, 0, std::uint64_t(
mesh.vertexCount) *
mesh.vertexStride);
412 pass.SetVertexBuffer(1, uvBuffer, 0,
uv.size() *
sizeof(
float));
413 if (
mesh.indexBuffer) {
414 const auto indexBytes =
mesh.indexFormat == wgpu::IndexFormat::Uint16 ? 2u : 4u;
415 pass.SetIndexBuffer(
mesh.indexBuffer,
mesh.indexFormat, 0, std::uint64_t(
mesh.indexCount) * indexBytes);
416 pass.DrawIndexed(
mesh.indexCount, 1, 0, 0, 0);
418 pass.Draw(
mesh.vertexCount, 1, 0, 0);
std::unordered_map< std::string, QuestRuntime > entries
std::map< std::string, Var > values
std::uint32_t vertexCount
vk::UniqueSampler sampler
std::vector< std::weak_ptr< DeviceBytes > > resources
std::unique_ptr< gpgpu::GpuBuffer > buffer
eve::action::ActionVfxBinding binding
const UnitySourceAsset & source
const AssetImportLimits & limits
static Diagnostic error(DiagnosticCode code, std::string message, std::string path={}, DiagnosticDetails details={}, std::string source={})
Construct an error diagnostic with the standard error severity.
EVENGINE_API_FOUNDATION public API.
Move-only operation result carrying either a value or Status.
static Result success(T value)
Construct a successful result owning value.
static Result failure(Status status)
Construct a failed result from a structured status.
GPU texture created via Graphics::newTexture. Owns GPU resources through an opaque backend handle.
uint32_t maxSamplersPerShaderStage() const
Maximum samplers visible to one shader stage.
uint32_t maxSampledTexturesPerShaderStage() const
Maximum sampled textures visible to one shader stage.
Result< void > setMesh3DPbrSurface(const PbrSurface *surface) override
Sets the mesh 3 d pbr surface.
PbrUniformGpu buildPbrUniformGpu(const PbrUniformBuildInputs &inputs)
Build all material and scene fields; mesh stream offsets remain disabled for the backend to fill.
Result< PbrVariantPlan > planPbrVariant(const PbrSurface &surface, bool environment, bool shadows, const PbrVariantLimits &limits)
Plan only resources reachable for an immutable PBR draw snapshot.
PbrSpecializedSources specializePbrVariantSources(const PbrVariantPlan &plan)
Specialize the generated WGSL for the resource reachability encoded by a checked plan.
FilterMode
Mag/min filter for texture sampling.
PbrCullMode
Raster face culling requested by a PBR material.
Result< void > validatePbrSurface(const PbrSurface &s)
Validate finite material factors and sampler enums without changing the input.
eve::BlendMode BlendMode
Compatibility alias for the shared 2D blend mode.
BlendMode
Render-neutral 2D blend mode shared by graphics-facing modules.
Owning material parameter snapshot; texture pointers remain borrowed. Base color/metallic/roughness c...
PbrVegetationMotion vegetationMotion
PbrVegetationExtras vegetationExtras
std::array< PbrTextureBinding, std::size_t(PbrTextureSlot::Count)> textures
PbrVegetationVertex vegetationVertex
PbrVegetationColors vegetationColors
PbrVegetationDetail vegetationDetail
PbrVegetationAlpha vegetationAlpha
std::array< PbrTextureBinding, 3 > textures
Vertex/index buffers for one mesh.
wgpu::BindGroupLayout layout
wgpu::RenderPipeline pipeline
std::map< SamplerKey, wgpu::Sampler > samplers
std::vector< Pipeline > pipelines
Device resource limits relevant to one extended PBR shader variant.
Stable resource-layout key and checked stage counts for a PBR snapshot.