Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
62 changes: 14 additions & 48 deletions Apps/Playground/Scripts/config.json
Original file line number Diff line number Diff line change
Expand Up @@ -1568,9 +1568,7 @@
"title": "Particles",
"playgroundId": "#G3ZYFU#7",
"renderCount": 100,
"referenceImage": "particles.png",
"excludeFromAutomaticTesting": true,
"reason": "Pixel diff after #1691 instance-data stride fix; not stride-related, needs follow-up."
"referenceImage": "particles.png"
},
{
"title": "PBRMetallicRoughnessMaterial",
Expand Down Expand Up @@ -1842,16 +1840,14 @@
{
"title": "Instances with color buffer",
"playgroundId": "#YPABS1#91",
"excludeFromAutomaticTesting": true,
"reason": "Pixel comparison fails (more than 20% pixels differ)",
"referenceImage": "instancecolors.png"
},
{
"title": "Prepass SSAO + particles",
"playgroundId": "#65MUMZ#47",
"renderCount": 50,
"excludeFromAutomaticTesting": true,
"reason": "Test crashes or hangs on Babylon Native",
"reason": "SSAO2 blur post-process shader fails to compile on desktop GL (samples uniform used as int loop bound); unrelated to instancing.",
"referenceImage": "prepass-ssao-particles.png"
},
{
Expand Down Expand Up @@ -2694,8 +2690,6 @@
"title": "halo-particle-system",
"playgroundId": "#2441BU#1",
"renderCount": 20,
"excludeFromAutomaticTesting": true,
"reason": "Pixel diff after #1691 instance-data stride fix; not stride-related, needs follow-up.",
"referenceImage": "halo-particle-system.png"
},
{
Expand All @@ -2710,8 +2704,6 @@
"title": "particle-system-with-custom-nme-shader",
"playgroundId": "#DMLLV2",
"renderCount": 5,
"excludeFromAutomaticTesting": true,
"reason": "Pixel diff after #1691 instance-data stride fix; not stride-related, needs follow-up.",
"referenceImage": "particle-system-with-custom-nme-shader.png"
},
{
Expand Down Expand Up @@ -4106,9 +4098,7 @@
"title": "Particles - Basic Properties - Color",
"playgroundId": "#0K3AQ2#3786",
"renderCount": 120,
"referenceImage": "particles-basic-color.png",
"excludeFromAutomaticTesting": true,
"reason": "Pixel diff after #1691 instance-data stride fix; not stride-related, needs follow-up."
"referenceImage": "particles-basic-color.png"
},
{
"title": "Particles - Basic Properties - Speed",
Expand Down Expand Up @@ -4138,9 +4128,7 @@
"title": "Particles - Basic Properties - Direction",
"playgroundId": "#0K3AQ2#3814",
"renderCount": 120,
"referenceImage": "particles-basic-direction.png",
"excludeFromAutomaticTesting": true,
"reason": "Pixel diff after #1691 instance-data stride fix; not stride-related, needs follow-up."
"referenceImage": "particles-basic-direction.png"
},
{
"title": "Particles - Basic Properties - Direction - Gravity",
Expand All @@ -4158,9 +4146,7 @@
"title": "Particles - Basic Properties - Emit Rate - Fast",
"playgroundId": "#0K3AQ2#3823",
"renderCount": 120,
"referenceImage": "particles-basic-emit-rate-fast.png",
"excludeFromAutomaticTesting": true,
"reason": "Pixel diff after #1691 instance-data stride fix; not stride-related, needs follow-up."
"referenceImage": "particles-basic-emit-rate-fast.png"
},
{
"title": "Particles - Basic Properties - Emission Limits",
Expand All @@ -4184,9 +4170,7 @@
"title": "Particles - Change - Size",
"playgroundId": "#0K3AQ2#3841",
"renderCount": 120,
"referenceImage": "particles-basic-change-size.png",
"excludeFromAutomaticTesting": true,
"reason": "Pixel diff after #1691 instance-data stride fix; not stride-related, needs follow-up."
"referenceImage": "particles-basic-change-size.png"
},
{
"title": "Particles - Change - Size 2",
Expand All @@ -4198,17 +4182,13 @@
"title": "Particles - Change - Color",
"playgroundId": "#0K3AQ2#3847",
"renderCount": 120,
"referenceImage": "particles-basic-change-color.png",
"excludeFromAutomaticTesting": true,
"reason": "Pixel diff after #1691 instance-data stride fix; not stride-related, needs follow-up."
"referenceImage": "particles-basic-change-color.png"
},
{
"title": "Particles - Change - Color 2",
"playgroundId": "#0K3AQ2#3851",
"renderCount": 120,
"referenceImage": "particles-basic-change-color-2.png",
"excludeFromAutomaticTesting": true,
"reason": "Pixel diff after #1691 instance-data stride fix; not stride-related, needs follow-up."
"referenceImage": "particles-basic-change-color-2.png"
},
{
"title": "Particles - Change - Speed",
Expand Down Expand Up @@ -4268,9 +4248,7 @@
"title": "Particles - Change - Lifetime",
"playgroundId": "#0K3AQ2#3885",
"renderCount": 120,
"referenceImage": "particles-basic-change-lifetime.png",
"excludeFromAutomaticTesting": true,
"reason": "Pixel diff after #1691 instance-data stride fix; not stride-related, needs follow-up."
"referenceImage": "particles-basic-change-lifetime.png"
},
{
"title": "Particles - Change - Lifetime 2",
Expand All @@ -4288,25 +4266,19 @@
"title": "Particles - Emitters - Point",
"playgroundId": "#WLX2I2#7",
"renderCount": 120,
"referenceImage": "particles-emitters-point.png",
"excludeFromAutomaticTesting": true,
"reason": "Pixel diff after #1691 instance-data stride fix; not stride-related, needs follow-up."
"referenceImage": "particles-emitters-point.png"
},
{
"title": "Particles - Emitters - Box",
"playgroundId": "#WLX2I2#8",
"renderCount": 120,
"referenceImage": "particles-emitters-box.png",
"excludeFromAutomaticTesting": true,
"reason": "Pixel diff after #1691 instance-data stride fix; not stride-related, needs follow-up."
"referenceImage": "particles-emitters-box.png"
},
{
"title": "Particles - Emitters - Sphere",
"playgroundId": "#WLX2I2#9",
"renderCount": 120,
"referenceImage": "particles-emitters-sphere.png",
"excludeFromAutomaticTesting": true,
"reason": "Pixel diff after #1691 instance-data stride fix; not stride-related, needs follow-up."
"referenceImage": "particles-emitters-sphere.png"
},
{
"title": "Particles - Emitters - Directed Sphere",
Expand All @@ -4324,9 +4296,7 @@
"title": "Particles - Emitters - Cylinder",
"playgroundId": "#WLX2I2#12",
"renderCount": 120,
"referenceImage": "particles-emitters-cylinder.png",
"excludeFromAutomaticTesting": true,
"reason": "Pixel diff after #1691 instance-data stride fix; not stride-related, needs follow-up."
"referenceImage": "particles-emitters-cylinder.png"
},
{
"title": "Particles - Emitters - Directed Cylinder",
Expand All @@ -4346,9 +4316,7 @@
"title": "Particles - Emitters - Directed Cone",
"playgroundId": "#WLX2I2#17",
"renderCount": 120,
"referenceImage": "particles-emitters-directed-cone.png",
"excludeFromAutomaticTesting": true,
"reason": "Pixel diff after #1691 instance-data stride fix; not stride-related, needs follow-up."
"referenceImage": "particles-emitters-directed-cone.png"
},
{
"title": "Particles - Emitters - Mesh",
Expand Down Expand Up @@ -4458,8 +4426,6 @@
"title": "Particles - Attractors",
"playgroundId": "#DEZ79M#73",
"renderCount": 240,
"excludeFromAutomaticTesting": true,
"reason": "Pixel diff after #1691 instance-data stride fix; not stride-related, needs follow-up.",
"referenceImage": "particles-attractors.png"
},
{
Expand Down
49 changes: 48 additions & 1 deletion Plugins/NativeEngine/Source/NativeEngine.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@

#include <Babylon/Graphics/Texture.h>

#include <map>
#include <string>

#include "JsConsoleLogger.h"

#include <arcana/threading/task.h>
Expand Down Expand Up @@ -970,6 +973,7 @@ namespace Babylon
Napi::Value jsProgram = Napi::Pointer<Program>::Create(info.Env(), program, Napi::NapiPointerDeleter(program));
try
{
program->SetSources(vertexSource, fragmentSource);
program->Initialize(m_shaderProvider.Get(vertexSource, fragmentSource));
}
catch (const std::exception& ex)
Expand All @@ -989,6 +993,8 @@ namespace Babylon
Program* program = new Program{m_deviceContext};
Napi::Value jsProgram = Napi::Pointer<Program>::Create(info.Env(), program, Napi::NapiPointerDeleter(program));

program->SetSources(vertexSource, fragmentSource);

arcana::make_task(arcana::threadpool_scheduler, *m_cancellationSource,
[this, vertexSource = std::move(vertexSource), fragmentSource = std::move(fragmentSource), program, cancellationSource{m_cancellationSource}]() {
program->Initialize(m_shaderProvider.Get(vertexSource, fragmentSource));
Expand Down Expand Up @@ -2335,6 +2341,47 @@ namespace Babylon
encoder->setUniform({it.first}, value.Data.data(), value.ElementLength);
}

// Divisor-driven instancing: a consumer-instanced attribute (divisor==1) recorded at a
// base bgfx location below TexCoord3 was compiled to a per-vertex slot. bgfx can only feed
// per-instance data into i_data slots (TexCoord3..TexCoord7), so route those attributes to
// the correct i_data slot via a lazily-compiled program variant. The target location mirrors
// BuildInstanceDataBuffer's reverse-attrib packing: highest base attrib -> TexCoord7.
bgfx::ProgramHandle programHandle = m_currentProgram->Handle();
if (m_boundVertexArray != nullptr)
{
const auto& instances = m_boundVertexArray->GetInstances();
if (!instances.empty())
{
std::map<std::string, uint32_t> genericInstancedAttributes;
const auto& attributeLocations = m_currentProgram->VertexAttributeLocations();
const size_t count = instances.size();
size_t ascendingIndex = 0;
for (const auto& instance : instances)
{
const bgfx::Attrib::Enum attrib = instance.first;
if (attrib < bgfx::Attrib::TexCoord3)
{
const size_t rank = count - 1 - ascendingIndex;
const uint32_t targetLocation = static_cast<uint32_t>(bgfx::Attrib::TexCoord7) - static_cast<uint32_t>(rank);
for (const auto& [name, location] : attributeLocations)
{
if (location == static_cast<uint32_t>(attrib))
{
genericInstancedAttributes.emplace(name, targetLocation);
break;
}
}
}
++ascendingIndex;
}

if (!genericInstancedAttributes.empty())
{
programHandle = m_currentProgram->GetOrCreateInstancedVariant(genericInstancedAttributes, m_shaderProvider);
}
}
}

auto& boundFrameBuffer = GetBoundFrameBuffer(*encoder);
if (boundFrameBuffer.HasDepth())
{
Expand All @@ -2348,7 +2395,7 @@ namespace Babylon
boundFrameBuffer.SetStencil(*encoder, m_stencilState);

// Discard everything except textures since we keep the state of everything else.
boundFrameBuffer.Submit(*encoder, m_currentProgram->Handle(), BGFX_DISCARD_ALL & ~BGFX_DISCARD_BINDINGS);
boundFrameBuffer.Submit(*encoder, programHandle, BGFX_DISCARD_ALL & ~BGFX_DISCARD_BINDINGS);
}

Graphics::UpdateToken& NativeEngine::GetUpdateToken()
Expand Down
67 changes: 54 additions & 13 deletions Plugins/NativeEngine/Source/Program.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,17 @@ namespace
uniformNameToIndex[info.name] = handleIndex;
}
}

bgfx::ShaderHandle CreateShader(const std::shared_ptr<Babylon::Graphics::BgfxShaderInfo>& shaderInfo, gsl::span<const uint8_t> bytes)
{
using ShaderInfoPtr = std::shared_ptr<Babylon::Graphics::BgfxShaderInfo>;
static auto ShaderInfoReleaseFn = [](void*, void* userData) {
delete reinterpret_cast<ShaderInfoPtr*>(userData);
};
return bgfx::createShader(bgfx::makeRef(
bytes.data(), static_cast<uint32_t>(bytes.size()),
ShaderInfoReleaseFn, new ShaderInfoPtr{shaderInfo}));
}
}

namespace Babylon
Expand All @@ -47,38 +58,68 @@ namespace Babylon
{
arcana::trace_region region{"Program::Initialize"};

using ShaderInfoPtr = std::shared_ptr<Graphics::BgfxShaderInfo>;

static auto ShaderInfoReleaseFn = [](void*, void* userData) {
delete reinterpret_cast<ShaderInfoPtr*>(userData);
};

auto vertexShader = bgfx::createShader(bgfx::makeRef(
shaderInfo->VertexBytes.data(), static_cast<uint32_t>(shaderInfo->VertexBytes.size()),
ShaderInfoReleaseFn, new ShaderInfoPtr{shaderInfo}));
auto vertexShader = CreateShader(shaderInfo, shaderInfo->VertexBytes);
InitUniformInfos(vertexShader, shaderInfo->UniformStages, m_uniformInfos, m_uniformNameToIndex);

auto fragmentShader = bgfx::createShader(bgfx::makeRef(
shaderInfo->FragmentBytes.data(), static_cast<uint32_t>(shaderInfo->FragmentBytes.size()),
ShaderInfoReleaseFn, new ShaderInfoPtr{shaderInfo}));
auto fragmentShader = CreateShader(shaderInfo, shaderInfo->FragmentBytes);
InitUniformInfos(fragmentShader, shaderInfo->UniformStages, m_uniformInfos, m_uniformNameToIndex);

m_handle = bgfx::createProgram(vertexShader, fragmentShader, true);
m_vertexAttributeLocations = shaderInfo->VertexAttributeLocations;
}

void Program::SetSources(std::string vertexSource, std::string fragmentSource)
{
m_vertexSource = std::move(vertexSource);
m_fragmentSource = std::move(fragmentSource);
}

bgfx::ProgramHandle Program::GetOrCreateInstancedVariant(const std::map<std::string, uint32_t>& instancedAttributes, ShaderProvider& shaderProvider)
{
if (instancedAttributes.empty())
{
return m_handle;
}

const auto it = m_instancedVariants.find(instancedAttributes);
if (it != m_instancedVariants.end())
{
return it->second;
}

auto shaderInfo = shaderProvider.Get(m_vertexSource, m_fragmentSource, instancedAttributes);

auto vertexShader = CreateShader(shaderInfo, shaderInfo->VertexBytes);
auto fragmentShader = CreateShader(shaderInfo, shaderInfo->FragmentBytes);
bgfx::ProgramHandle handle = bgfx::createProgram(vertexShader, fragmentShader, true);

m_instancedVariants.emplace(instancedAttributes, handle);
return handle;
}

void Program::Dispose()
{
const bool sameDevice = m_deviceID == m_deviceContext.GetDeviceId();

if (bgfx::isValid(m_handle))
{
if (m_deviceID == m_deviceContext.GetDeviceId())
if (sameDevice)
{
bgfx::destroy(m_handle);
}

m_handle = BGFX_INVALID_HANDLE;
}

for (auto& [key, handle] : m_instancedVariants)
{
if (sameDevice && bgfx::isValid(handle))
{
bgfx::destroy(handle);
}
}
m_instancedVariants.clear();

m_uniforms.clear();
m_uniformNameToIndex.clear();
m_uniformInfos.clear();
Expand Down
12 changes: 12 additions & 0 deletions Plugins/NativeEngine/Source/Program.h
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,15 @@ namespace Babylon
void Initialize(std::shared_ptr<Graphics::BgfxShaderInfo> shaderInfo);
void Dispose();

// Stores the original GLSL sources so divisor-driven instanced variants can be
// recompiled lazily on the first instanced draw (see GetOrCreateInstancedVariant).
void SetSources(std::string vertexSource, std::string fragmentSource);

// Returns a program handle whose vertex shader routes the given consumer-instanced
// attributes (name -> bgfx per-instance location) to bgfx i_data slots. Compiled and
// cached on first use. An empty map returns the base handle.
bgfx::ProgramHandle GetOrCreateInstancedVariant(const std::map<std::string, uint32_t>& instancedAttributes, ShaderProvider& shaderProvider);

void SetUniform(bgfx::UniformHandle handle, gsl::span<const float> data, size_t elementLength = 1);
const UniformInfo* GetUniformInfo(const std::string& name) const;
bgfx::ProgramHandle Handle() const { return m_handle; }
Expand All @@ -69,5 +78,8 @@ namespace Babylon
std::map<std::string, uint16_t> m_uniformNameToIndex;
std::map<uint16_t, UniformInfo> m_uniformInfos;
std::map<std::string, uint32_t> m_vertexAttributeLocations;
std::string m_vertexSource;
std::string m_fragmentSource;
std::map<std::map<std::string, uint32_t>, bgfx::ProgramHandle> m_instancedVariants;
};
}
Loading