engine: sync editor rendering and ui changes

This commit is contained in:
2026-04-08 16:09:15 +08:00
parent 31756847ab
commit 162f1cc12e
153 changed files with 4454 additions and 2990 deletions

View File

@@ -394,8 +394,6 @@ add_library(XCEngine STATIC
${CMAKE_CURRENT_SOURCE_DIR}/src/Resources/Shader/Internal/ShaderRuntimeBuildUtils.cpp
${CMAKE_CURRENT_SOURCE_DIR}/src/Resources/Shader/Internal/ShaderAuthoringLoader.h
${CMAKE_CURRENT_SOURCE_DIR}/src/Resources/Shader/Internal/ShaderAuthoringLoader.cpp
${CMAKE_CURRENT_SOURCE_DIR}/src/Resources/Shader/Internal/ShaderManifestLoader.h
${CMAKE_CURRENT_SOURCE_DIR}/src/Resources/Shader/Internal/ShaderManifestLoader.cpp
${CMAKE_CURRENT_SOURCE_DIR}/src/Resources/Shader/Internal/ShaderArtifactLoader.h
${CMAKE_CURRENT_SOURCE_DIR}/src/Resources/Shader/Internal/ShaderArtifactLoader.cpp
${CMAKE_CURRENT_SOURCE_DIR}/src/Resources/Shader/Internal/ShaderAuthoringInternal.h
@@ -569,12 +567,14 @@ add_library(XCEngine STATIC
${CMAKE_CURRENT_SOURCE_DIR}/include/XCEngine/UI/Widgets/UIKeyboardNavigationModel.h
${CMAKE_CURRENT_SOURCE_DIR}/include/XCEngine/UI/Widgets/UIPopupOverlayModel.h
${CMAKE_CURRENT_SOURCE_DIR}/include/XCEngine/UI/Widgets/UIPropertyEditModel.h
${CMAKE_CURRENT_SOURCE_DIR}/include/XCEngine/UI/Widgets/UIScrollModel.h
${CMAKE_CURRENT_SOURCE_DIR}/include/XCEngine/UI/Widgets/UISelectionModel.h
${CMAKE_CURRENT_SOURCE_DIR}/include/XCEngine/UI/Widgets/UITabStripModel.h
${CMAKE_CURRENT_SOURCE_DIR}/src/UI/Widgets/UIExpansionModel.cpp
${CMAKE_CURRENT_SOURCE_DIR}/src/UI/Widgets/UIKeyboardNavigationModel.cpp
${CMAKE_CURRENT_SOURCE_DIR}/src/UI/Widgets/UIPopupOverlayModel.cpp
${CMAKE_CURRENT_SOURCE_DIR}/src/UI/Widgets/UIPropertyEditModel.cpp
${CMAKE_CURRENT_SOURCE_DIR}/src/UI/Widgets/UIScrollModel.cpp
${CMAKE_CURRENT_SOURCE_DIR}/src/UI/Widgets/UISelectionModel.cpp
${CMAKE_CURRENT_SOURCE_DIR}/include/XCEngine/UI/Runtime/UIScreenTypes.h
${CMAKE_CURRENT_SOURCE_DIR}/include/XCEngine/UI/Runtime/UIScreenDocumentHost.h

View File

@@ -46,7 +46,14 @@ Shader "Builtin Object Id Outline"
bool IsSelectedObject(float4 objectIdColor)
{
if (objectIdColor.a <= 0.0) {
// Object-id surfaces encode the low 32 bits across RGBA, so low-valued
// runtime ids legitimately have zero alpha. Only the all-zero clear
// color should be treated as "no object".
if (all(abs(objectIdColor) <= float4(
0.0025,
0.0025,
0.0025,
0.0025))) {
return false;
}

View File

@@ -1,6 +1,7 @@
#pragma once
#include "Math.h"
#include "Vector3.h"
namespace XCEngine {
namespace Math {

View File

@@ -22,7 +22,7 @@ enum class KeyCode : Core::uint8 {
Up = 126, Down = 125, Left = 123, Right = 124,
Home = 115, End = 119, PageUp = 116, PageDown = 121,
Delete = 51, Backspace = 51,
Delete = 117, Backspace = 51,
Zero = 39, One = 30, Two = 31, Three = 32,
Four = 33, Five = 34, Six = 35, Seven = 37,

View File

@@ -26,6 +26,7 @@ public:
bool IsValid() const override;
void SetViewType(ResourceViewType type) { m_viewType = type; }
void SetFormat(Format format) { m_format = format; }
ResourceViewType GetViewType() const override { return m_viewType; }
ResourceViewDimension GetDimension() const override { return m_dimension; }
Format GetFormat() const override { return m_format; }

View File

@@ -19,10 +19,14 @@ struct OpenGLDepthStencilState {
uint8_t stencilReadMask = 0xFF;
uint8_t stencilWriteMask = 0xFF;
int stencilRef = 0;
ComparisonFunc stencilFunc = ComparisonFunc::Always;
StencilOp stencilFailOp = StencilOp::Keep;
StencilOp stencilDepthFailOp = StencilOp::Keep;
StencilOp stencilDepthPassOp = StencilOp::Keep;
ComparisonFunc frontStencilFunc = ComparisonFunc::Always;
StencilOp frontStencilFailOp = StencilOp::Keep;
StencilOp frontStencilDepthFailOp = StencilOp::Keep;
StencilOp frontStencilDepthPassOp = StencilOp::Keep;
ComparisonFunc backStencilFunc = ComparisonFunc::Always;
StencilOp backStencilFailOp = StencilOp::Keep;
StencilOp backStencilDepthFailOp = StencilOp::Keep;
StencilOp backStencilDepthPassOp = StencilOp::Keep;
};
struct OpenGLBlendState {

View File

@@ -87,6 +87,7 @@ private:
bool m_renderPassActive = false;
bool m_hasViewport = false;
bool m_hasScissor = false;
uint32_t m_stencilRef = 0;
VkViewport m_viewport = {};
VkRect2D m_scissor = {};
std::vector<VkFramebuffer> m_transientFramebuffers;

View File

@@ -18,6 +18,9 @@ enum class BuiltinMaterialPass : Core::uint32 {
DepthOnly,
ShadowCaster,
ObjectId,
Skybox,
PostProcess,
FinalColor,
Forward = ForwardLit
};
@@ -36,7 +39,12 @@ enum class BuiltinPassResourceSemantic : Core::uint8 {
Material,
Lighting,
ShadowReceiver,
Environment,
PassConstants,
BaseColorTexture,
SourceColorTexture,
SkyboxPanoramicTexture,
SkyboxTexture,
ShadowMapTexture,
LinearClampSampler,
ShadowMapSampler
@@ -60,7 +68,12 @@ struct BuiltinPassResourceBindingPlan {
PassResourceBindingLocation material = {};
PassResourceBindingLocation lighting = {};
PassResourceBindingLocation shadowReceiver = {};
PassResourceBindingLocation environment = {};
PassResourceBindingLocation passConstants = {};
PassResourceBindingLocation baseColorTexture = {};
PassResourceBindingLocation sourceColorTexture = {};
PassResourceBindingLocation skyboxPanoramicTexture = {};
PassResourceBindingLocation skyboxTexture = {};
PassResourceBindingLocation linearClampSampler = {};
PassResourceBindingLocation shadowMapTexture = {};
PassResourceBindingLocation shadowMapSampler = {};
@@ -85,8 +98,13 @@ struct BuiltinPassSetLayoutMetadata {
bool usesMaterial = false;
bool usesLighting = false;
bool usesShadowReceiver = false;
bool usesEnvironment = false;
bool usesPassConstants = false;
bool usesTexture = false;
bool usesBaseColorTexture = false;
bool usesSourceColorTexture = false;
bool usesSkyboxPanoramicTexture = false;
bool usesSkyboxTexture = false;
bool usesShadowMapTexture = false;
bool usesSampler = false;
bool usesLinearClampSampler = false;

View File

@@ -1,5 +1,8 @@
#pragma once
#include <cstring>
#include <XCEngine/RHI/RHICommandList.h>
#include <XCEngine/RHI/RHITypes.h>
#include <XCEngine/Resources/Material/Material.h>
#include <XCEngine/Resources/Shader/Shader.h>
@@ -97,6 +100,28 @@ inline RHI::BlendOp ToRHIBlendOp(Resources::MaterialBlendOp op) {
}
}
inline RHI::StencilOp ToRHIStencilOp(Resources::MaterialStencilOp op) {
switch (op) {
case Resources::MaterialStencilOp::Zero:
return RHI::StencilOp::Zero;
case Resources::MaterialStencilOp::Replace:
return RHI::StencilOp::Replace;
case Resources::MaterialStencilOp::IncrSat:
return RHI::StencilOp::IncrSat;
case Resources::MaterialStencilOp::DecrSat:
return RHI::StencilOp::DecrSat;
case Resources::MaterialStencilOp::Invert:
return RHI::StencilOp::Invert;
case Resources::MaterialStencilOp::IncrWrap:
return RHI::StencilOp::Incr;
case Resources::MaterialStencilOp::DecrWrap:
return RHI::StencilOp::Decr;
case Resources::MaterialStencilOp::Keep:
default:
return RHI::StencilOp::Keep;
}
}
inline Resources::MaterialRenderState ResolveEffectiveRenderState(
const Resources::ShaderPass* shaderPass,
const Resources::Material* material) {
@@ -122,6 +147,8 @@ inline RHI::RasterizerDesc BuildRasterizerState(const Resources::MaterialRenderS
desc.frontFace = static_cast<uint32_t>(RHI::FrontFace::CounterClockwise);
desc.depthClipEnable = true;
desc.cullMode = static_cast<uint32_t>(ToRHICullMode(renderState.cullMode));
desc.depthBias = renderState.depthBiasUnits;
desc.slopeScaledDepthBias = renderState.depthBiasFactor;
return desc;
}
@@ -145,7 +172,17 @@ inline RHI::DepthStencilStateDesc BuildDepthStencilState(const Resources::Materi
desc.depthTestEnable = renderState.depthTestEnable;
desc.depthWriteEnable = renderState.depthWriteEnable;
desc.depthFunc = static_cast<uint32_t>(ToRHIComparisonFunc(renderState.depthFunc));
desc.stencilEnable = false;
desc.stencilEnable = renderState.stencil.enabled;
desc.stencilReadMask = renderState.stencil.readMask;
desc.stencilWriteMask = renderState.stencil.writeMask;
desc.front.failOp = static_cast<uint32_t>(ToRHIStencilOp(renderState.stencil.front.failOp));
desc.front.passOp = static_cast<uint32_t>(ToRHIStencilOp(renderState.stencil.front.passOp));
desc.front.depthFailOp = static_cast<uint32_t>(ToRHIStencilOp(renderState.stencil.front.depthFailOp));
desc.front.func = static_cast<uint32_t>(ToRHIComparisonFunc(renderState.stencil.front.func));
desc.back.failOp = static_cast<uint32_t>(ToRHIStencilOp(renderState.stencil.back.failOp));
desc.back.passOp = static_cast<uint32_t>(ToRHIStencilOp(renderState.stencil.back.passOp));
desc.back.depthFailOp = static_cast<uint32_t>(ToRHIStencilOp(renderState.stencil.back.depthFailOp));
desc.back.func = static_cast<uint32_t>(ToRHIComparisonFunc(renderState.stencil.back.func));
return desc;
}
@@ -167,12 +204,32 @@ inline void ApplyResolvedRenderState(
ApplyRenderState(ResolveEffectiveRenderState(shaderPass, material), pipelineDesc);
}
inline void ApplyDynamicRenderState(
const Resources::MaterialRenderState& renderState,
RHI::RHICommandList& commandList) {
if (renderState.stencil.enabled) {
commandList.SetStencilRef(renderState.stencil.reference);
}
}
inline Resources::MaterialRenderState BuildStaticPipelineRenderStateKey(
const Resources::MaterialRenderState& renderState) {
Resources::MaterialRenderState keyState = renderState;
keyState.stencil.reference = 0;
return keyState;
}
struct MaterialRenderStateHash {
size_t operator()(const Resources::MaterialRenderState& state) const noexcept {
size_t hash = 2166136261u;
auto combine = [&hash](size_t value) {
hash ^= value + 0x9e3779b9u + (hash << 6) + (hash >> 2);
};
auto combineFloat = [&combine](float value) {
Core::uint32 bits = 0;
std::memcpy(&bits, &value, sizeof(bits));
combine(static_cast<size_t>(bits));
};
combine(static_cast<size_t>(state.blendEnable));
combine(static_cast<size_t>(state.srcBlend));
@@ -186,6 +243,20 @@ struct MaterialRenderStateHash {
combine(static_cast<size_t>(state.depthWriteEnable));
combine(static_cast<size_t>(state.depthFunc));
combine(static_cast<size_t>(state.cullMode));
combineFloat(state.depthBiasFactor);
combine(static_cast<size_t>(state.depthBiasUnits));
combine(static_cast<size_t>(state.stencil.enabled));
combine(static_cast<size_t>(state.stencil.readMask));
combine(static_cast<size_t>(state.stencil.writeMask));
combine(static_cast<size_t>(state.stencil.reference));
combine(static_cast<size_t>(state.stencil.front.failOp));
combine(static_cast<size_t>(state.stencil.front.passOp));
combine(static_cast<size_t>(state.stencil.front.depthFailOp));
combine(static_cast<size_t>(state.stencil.front.func));
combine(static_cast<size_t>(state.stencil.back.failOp));
combine(static_cast<size_t>(state.stencil.back.passOp));
combine(static_cast<size_t>(state.stencil.back.depthFailOp));
combine(static_cast<size_t>(state.stencil.back.func));
return hash;
}
};

View File

@@ -102,16 +102,6 @@ public:
bool HasRenderStateOverride() const { return m_hasRenderStateOverride; }
void SetRenderStateOverrideEnabled(bool enabled);
// Legacy-only compatibility hint for older material assets that still
// select a builtin pass by serialized name.
void SetLegacyShaderPassHint(const Containers::String& shaderPass);
const Containers::String& GetLegacyShaderPassHint() const { return m_legacyShaderPassHint; }
bool HasLegacyShaderPassHint() const { return !m_legacyShaderPassHint.Empty(); }
void ClearLegacyShaderPassHint();
void SetShaderPass(const Containers::String& shaderPass);
const Containers::String& GetShaderPass() const { return GetLegacyShaderPassHint(); }
void SetTag(const Containers::String& name, const Containers::String& value);
Containers::String GetTag(const Containers::String& name) const;
bool HasTag(const Containers::String& name) const;
@@ -186,7 +176,6 @@ private:
Core::int32 m_renderQueue = static_cast<Core::int32>(MaterialRenderQueue::Geometry);
MaterialRenderState m_renderState;
bool m_hasRenderStateOverride = false;
Containers::String m_legacyShaderPassHint;
Containers::Array<MaterialTagEntry> m_tags;
ShaderKeywordSet m_keywordSet;
Containers::HashMap<Containers::String, MaterialProperty> m_properties;

View File

@@ -50,6 +50,57 @@ enum class MaterialBlendFactor : Core::uint8 {
InvSrc1Alpha = 16
};
enum class MaterialStencilOp : Core::uint8 {
Keep = 0,
Zero = 1,
Replace = 2,
IncrSat = 3,
DecrSat = 4,
Invert = 5,
IncrWrap = 6,
DecrWrap = 7
};
struct MaterialStencilFaceState {
MaterialStencilOp failOp = MaterialStencilOp::Keep;
MaterialStencilOp passOp = MaterialStencilOp::Keep;
MaterialStencilOp depthFailOp = MaterialStencilOp::Keep;
MaterialComparisonFunc func = MaterialComparisonFunc::Always;
bool operator==(const MaterialStencilFaceState& other) const {
return failOp == other.failOp &&
passOp == other.passOp &&
depthFailOp == other.depthFailOp &&
func == other.func;
}
bool operator!=(const MaterialStencilFaceState& other) const {
return !(*this == other);
}
};
struct MaterialStencilState {
bool enabled = false;
Core::uint8 readMask = 0xFF;
Core::uint8 writeMask = 0xFF;
Core::uint8 reference = 0;
MaterialStencilFaceState front = {};
MaterialStencilFaceState back = {};
bool operator==(const MaterialStencilState& other) const {
return enabled == other.enabled &&
readMask == other.readMask &&
writeMask == other.writeMask &&
reference == other.reference &&
front == other.front &&
back == other.back;
}
bool operator!=(const MaterialStencilState& other) const {
return !(*this == other);
}
};
struct MaterialRenderState {
bool blendEnable = false;
MaterialBlendFactor srcBlend = MaterialBlendFactor::One;
@@ -65,6 +116,9 @@ struct MaterialRenderState {
MaterialComparisonFunc depthFunc = MaterialComparisonFunc::Less;
MaterialCullMode cullMode = MaterialCullMode::None;
float depthBiasFactor = 0.0f;
Core::int32 depthBiasUnits = 0;
MaterialStencilState stencil = {};
bool operator==(const MaterialRenderState& other) const {
return blendEnable == other.blendEnable &&
@@ -78,7 +132,10 @@ struct MaterialRenderState {
depthTestEnable == other.depthTestEnable &&
depthWriteEnable == other.depthWriteEnable &&
depthFunc == other.depthFunc &&
cullMode == other.cullMode;
cullMode == other.cullMode &&
depthBiasFactor == other.depthBiasFactor &&
depthBiasUnits == other.depthBiasUnits &&
stencil == other.stencil;
}
bool operator!=(const MaterialRenderState& other) const {

View File

@@ -7,6 +7,7 @@
#include "Mutex.h"
#include "SpinLock.h"
#include <algorithm>
#include <memory>
#include <vector>
#include <queue>
#include <thread>
@@ -102,7 +103,9 @@ void TaskSystem::ParallelFor(int32_t start, int32_t end, Func&& func) {
}
for (auto& task : tasks) {
Submit(std::make_unique<LambdaTask<std::function<void()>>>(std::move(task), TaskPriority::High));
Submit(std::make_unique<::XCEngine::Threading::LambdaTask<std::function<void()>>>(
std::move(task),
TaskPriority::High));
}
}

View File

@@ -29,12 +29,21 @@ struct UIColor {
enum class UIDrawCommandType : std::uint8_t {
FilledRect = 0,
RectOutline,
FilledRectLinearGradient,
Line,
FilledCircle,
CircleOutline,
Text,
Image,
PushClipRect,
PopClipRect
};
enum class UILinearGradientDirection : std::uint8_t {
Horizontal = 0,
Vertical
};
struct UIDrawCommand {
UIDrawCommandType type = UIDrawCommandType::FilledRect;
UIRect rect = {};
@@ -42,10 +51,13 @@ struct UIDrawCommand {
UIPoint uvMin = {};
UIPoint uvMax = UIPoint(1.0f, 1.0f);
UIColor color = {};
UIColor secondaryColor = {};
float thickness = 1.0f;
float rounding = 0.0f;
float radius = 0.0f;
float fontSize = 0.0f;
bool intersectWithCurrentClip = true;
UILinearGradientDirection gradientDirection = UILinearGradientDirection::Horizontal;
UITextureHandle texture = {};
std::string text;
};
@@ -107,6 +119,62 @@ public:
return AddCommand(std::move(command));
}
UIDrawCommand& AddFilledRectLinearGradient(
const UIRect& rect,
const UIColor& startColor,
const UIColor& endColor,
UILinearGradientDirection direction = UILinearGradientDirection::Horizontal,
float rounding = 0.0f) {
UIDrawCommand command = {};
command.type = UIDrawCommandType::FilledRectLinearGradient;
command.rect = rect;
command.color = startColor;
command.secondaryColor = endColor;
command.rounding = rounding;
command.gradientDirection = direction;
return AddCommand(std::move(command));
}
UIDrawCommand& AddLine(
const UIPoint& start,
const UIPoint& end,
const UIColor& color,
float thickness = 1.0f) {
UIDrawCommand command = {};
command.type = UIDrawCommandType::Line;
command.position = start;
command.uvMin = end;
command.color = color;
command.thickness = thickness;
return AddCommand(std::move(command));
}
UIDrawCommand& AddFilledCircle(
const UIPoint& center,
float radius,
const UIColor& color) {
UIDrawCommand command = {};
command.type = UIDrawCommandType::FilledCircle;
command.position = center;
command.radius = radius;
command.color = color;
return AddCommand(std::move(command));
}
UIDrawCommand& AddCircleOutline(
const UIPoint& center,
float radius,
const UIColor& color,
float thickness = 1.0f) {
UIDrawCommand command = {};
command.type = UIDrawCommandType::CircleOutline;
command.position = center;
command.radius = radius;
command.color = color;
command.thickness = thickness;
return AddCommand(std::move(command));
}
UIDrawCommand& AddText(
const UIPoint& position,
std::string text,

View File

@@ -0,0 +1,32 @@
#pragma once
namespace XCEngine {
namespace UI {
namespace Widgets {
struct UIScrollWheelResult {
bool changed = false;
float overflow = 0.0f;
float offsetBefore = 0.0f;
float offsetAfter = 0.0f;
};
float ComputeUIScrollOverflow(float contentExtent, float viewportExtent);
float ClampUIScrollOffset(float offset, float contentExtent, float viewportExtent);
UIScrollWheelResult ApplyUIScrollWheel(
float offset,
float wheelDelta,
float contentExtent,
float viewportExtent,
float wheelStep = 48.0f,
float epsilon = 0.01f);
float EnsureUIScrollOffsetVisible(
float offset,
float itemStart,
float itemExtent,
float contentExtent,
float viewportExtent);
} // namespace Widgets
} // namespace UI
} // namespace XCEngine

View File

@@ -10,6 +10,45 @@ namespace XCEngine {
namespace Audio {
namespace WASAPI {
namespace {
std::string ConvertWaveOutDeviceName(const TCHAR* deviceName) {
if (deviceName == nullptr) {
return {};
}
#if defined(UNICODE) || defined(_UNICODE)
const int requiredBytes = WideCharToMultiByte(
CP_UTF8,
0,
deviceName,
-1,
nullptr,
0,
nullptr,
nullptr);
if (requiredBytes <= 1) {
return {};
}
std::string utf8Name(static_cast<std::size_t>(requiredBytes - 1), '\0');
WideCharToMultiByte(
CP_UTF8,
0,
deviceName,
-1,
utf8Name.data(),
requiredBytes,
nullptr,
nullptr);
return utf8Name;
#else
return std::string(deviceName);
#endif
}
} // namespace
WASAPIBackend::WASAPIBackend()
: m_audioBuffer1(BufferSize * 2)
, m_audioBuffer2(BufferSize * 2)
@@ -65,7 +104,7 @@ std::string WASAPIBackend::GetDeviceName() const {
void WASAPIBackend::GetAvailableDevices(std::vector<std::string>& devices) {
devices.clear();
for (const auto& caps : m_waveOutCaps) {
devices.push_back(caps.szPname);
devices.push_back(ConvertWaveOutDeviceName(caps.szPname));
}
}
@@ -73,7 +112,7 @@ bool WASAPIBackend::SetDevice(const std::string& deviceName) {
for (UINT i = 0; i < waveOutGetNumDevs(); ++i) {
WAVEOUTCAPS caps;
waveOutGetDevCaps(i, &caps, sizeof(WAVEOUTCAPS));
if (deviceName == caps.szPname) {
if (deviceName == ConvertWaveOutDeviceName(caps.szPname)) {
m_deviceName = deviceName;
return true;
}

View File

@@ -139,7 +139,7 @@ void MeshFilterComponent::Serialize(std::ostream& os) const {
}
os << "meshRef=" << EncodeAssetRef(meshRef) << ";";
if (!meshRef.IsValid() && !m_meshPath.empty()) {
if (!meshRef.IsValid() && !m_meshPath.empty() && HasVirtualPathScheme(m_meshPath)) {
os << "meshPath=" << m_meshPath << ";";
}
}
@@ -167,7 +167,7 @@ void MeshFilterComponent::Deserialize(std::istream& is) {
const std::string key = token.substr(0, eqPos);
const std::string value = token.substr(eqPos + 1);
if (key == "mesh" || key == "meshPath") {
if (key == "meshPath") {
pendingMeshPath = value;
} else if (key == "meshRef") {
TryDecodeAssetRef(value, pendingMeshRef);
@@ -203,12 +203,9 @@ void MeshFilterComponent::Deserialize(std::istream& is) {
} else {
m_meshPath = pendingMeshPath;
}
} else if (!pendingMeshPath.empty()) {
} else if (!pendingMeshPath.empty() && HasVirtualPathScheme(pendingMeshPath)) {
if (Resources::ResourceManager::Get().IsDeferredSceneLoadEnabled()) {
m_meshPath = pendingMeshPath;
if (!Resources::ResourceManager::Get().TryGetAssetRef(m_meshPath.c_str(), Resources::ResourceType::Mesh, m_meshRef)) {
m_meshRef.Reset();
}
return;
}

View File

@@ -240,7 +240,6 @@ void MeshRendererComponent::Serialize(std::ostream& os) const {
serializedRefs.resize(slotCount);
std::vector<std::string> serializedPaths = m_materialPaths;
serializedPaths.resize(slotCount);
std::vector<std::string> fallbackPaths(slotCount);
for (size_t i = 0; i < slotCount; ++i) {
if (!serializedRefs[i].IsValid() &&
!serializedPaths[i].empty() &&
@@ -251,8 +250,8 @@ void MeshRendererComponent::Serialize(std::ostream& os) const {
serializedRefs[i])) {
}
if (!serializedRefs[i].IsValid()) {
fallbackPaths[i] = serializedPaths[i];
if (serializedRefs[i].IsValid() || !HasVirtualPathScheme(serializedPaths[i])) {
serializedPaths[i].clear();
}
}
@@ -261,7 +260,7 @@ void MeshRendererComponent::Serialize(std::ostream& os) const {
if (i > 0) {
os << "|";
}
os << fallbackPaths[i];
os << serializedPaths[i];
}
os << ";";
os << "materialRefs=";
@@ -298,7 +297,7 @@ void MeshRendererComponent::Deserialize(std::istream& is) {
const std::string key = token.substr(0, eqPos);
const std::string value = token.substr(eqPos + 1);
if (key == "materials" || key == "materialPaths") {
if (key == "materialPaths") {
m_materialPaths = SplitMaterialPaths(value);
m_materials.resize(m_materialPaths.size());
m_materialRefs.resize(m_materialPaths.size());
@@ -362,15 +361,12 @@ void MeshRendererComponent::Deserialize(std::istream& is) {
}
}
if (!HasVirtualPathScheme(m_materialPaths[i])) {
m_materialPaths[i].clear();
continue;
}
if (Resources::ResourceManager::Get().IsDeferredSceneLoadEnabled()) {
if (!m_materialPaths[i].empty()) {
if (!m_materialRefs[i].IsValid() &&
!Resources::ResourceManager::Get().TryGetAssetRef(m_materialPaths[i].c_str(),
Resources::ResourceType::Material,
m_materialRefs[i])) {
m_materialRefs[i].Reset();
}
}
continue;
}

View File

@@ -1,11 +1,76 @@
#include "XCEngine/RHI/D3D12/D3D12PipelineState.h"
#include "XCEngine/RHI/D3D12/D3D12Shader.h"
#include "XCEngine/Debug/Logger.h"
#include <d3d12sdklayers.h>
#include <cstring>
#include <vector>
namespace XCEngine {
namespace RHI {
namespace {
void LogD3D12InfoQueueMessages(ID3D12Device* device) {
if (device == nullptr) {
return;
}
ComPtr<ID3D12InfoQueue> infoQueue;
if (FAILED(device->QueryInterface(IID_PPV_ARGS(&infoQueue))) || infoQueue == nullptr) {
return;
}
const UINT64 messageCount = infoQueue->GetNumStoredMessagesAllowedByRetrievalFilter();
if (messageCount == 0) {
return;
}
const UINT64 firstMessage = messageCount > 12 ? messageCount - 12 : 0;
for (UINT64 messageIndex = firstMessage; messageIndex < messageCount; ++messageIndex) {
SIZE_T messageLength = 0;
if (FAILED(infoQueue->GetMessage(messageIndex, nullptr, &messageLength)) || messageLength == 0) {
continue;
}
std::vector<unsigned char> storage(messageLength);
auto* message = reinterpret_cast<D3D12_MESSAGE*>(storage.data());
if (FAILED(infoQueue->GetMessage(messageIndex, message, &messageLength)) || message == nullptr) {
continue;
}
if (message->pDescription != nullptr && message->pDescription[0] != '\0') {
Debug::Logger::Get().Error(Debug::LogCategory::Rendering, message->pDescription);
}
}
infoQueue->ClearStoredMessages();
}
void LogGraphicsPipelineFailureDetails(
ID3D12Device* device,
HRESULT pipelineCreateResult,
const D3D12_GRAPHICS_PIPELINE_STATE_DESC& desc) {
char details[512] = {};
sprintf_s(
details,
"D3D12 PSO failure details: createHr=0x%08X removedReason=0x%08X rootSignature=%p rtCount=%u rtv0=0x%X dsv=0x%X sampleCount=%u vsBytes=%zu psBytes=%zu gsBytes=%zu",
static_cast<unsigned int>(pipelineCreateResult),
static_cast<unsigned int>(device != nullptr ? device->GetDeviceRemovedReason() : S_OK),
desc.pRootSignature,
desc.NumRenderTargets,
static_cast<unsigned int>(desc.RTVFormats[0]),
static_cast<unsigned int>(desc.DSVFormat),
desc.SampleDesc.Count,
static_cast<size_t>(desc.VS.BytecodeLength),
static_cast<size_t>(desc.PS.BytecodeLength),
static_cast<size_t>(desc.GS.BytecodeLength));
Debug::Logger::Get().Error(Debug::LogCategory::Rendering, details);
LogD3D12InfoQueueMessages(device);
}
} // namespace
D3D12PipelineState::D3D12PipelineState(ID3D12Device* device)
: m_device(device), m_finalized(false) {
m_renderTargetFormats[0] = static_cast<uint32_t>(Format::R8G8B8A8_UNorm);
@@ -176,6 +241,9 @@ bool D3D12PipelineState::CreateD3D12PSO() {
desc.RasterizerState.DepthClipEnable = m_rasterizerDesc.depthClipEnable;
desc.RasterizerState.MultisampleEnable = m_rasterizerDesc.multisampleEnable;
desc.RasterizerState.AntialiasedLineEnable = m_rasterizerDesc.antialiasedLineEnable;
desc.RasterizerState.DepthBias = m_rasterizerDesc.depthBias;
desc.RasterizerState.DepthBiasClamp = m_rasterizerDesc.depthBiasClamp;
desc.RasterizerState.SlopeScaledDepthBias = m_rasterizerDesc.slopeScaledDepthBias;
desc.BlendState.RenderTarget[0].BlendEnable = m_blendDesc.blendEnable;
desc.BlendState.RenderTarget[0].SrcBlend = ToD3D12(static_cast<BlendFactor>(m_blendDesc.srcBlend));
@@ -216,6 +284,7 @@ bool D3D12PipelineState::CreateD3D12PSO() {
char errorMessage[256] = {};
sprintf_s(errorMessage, "D3D12 CreateGraphicsPipelineState failed: hr=0x%08X", static_cast<unsigned int>(hr));
Debug::Logger::Get().Error(Debug::LogCategory::Rendering, errorMessage);
LogGraphicsPipelineFailureDetails(m_device, hr, desc);
return false;
}

View File

@@ -319,17 +319,26 @@ void OpenGLCommandList::SetPrimitiveType(PrimitiveType type) {
void OpenGLCommandList::Draw(PrimitiveType type, unsigned int vertexCount, unsigned int startVertex) {
m_primitiveType = ToOpenGL(type);
if (m_currentVAO == 0) {
EnsureInternalVertexArrayBound();
}
glDrawArrays(m_primitiveType, startVertex, vertexCount);
}
void OpenGLCommandList::DrawInstanced(PrimitiveType type, unsigned int vertexCount, unsigned int instanceCount, unsigned int startVertex, unsigned int startInstance) {
m_primitiveType = ToOpenGL(type);
if (m_currentVAO == 0) {
EnsureInternalVertexArrayBound();
}
glDrawArraysInstanced(m_primitiveType, startVertex, vertexCount, instanceCount);
(void)startInstance;
}
void OpenGLCommandList::DrawIndexed(PrimitiveType type, unsigned int indexCount, unsigned int startIndex, int baseVertex) {
m_primitiveType = ToOpenGL(type);
if (m_currentVAO == 0) {
EnsureInternalVertexArrayBound();
}
const uint64_t indexOffset = m_currentIndexOffset + static_cast<uint64_t>(startIndex) * GetIndexTypeSize(m_currentIndexType);
glDrawElements(m_primitiveType,
indexCount,
@@ -340,6 +349,9 @@ void OpenGLCommandList::DrawIndexed(PrimitiveType type, unsigned int indexCount,
void OpenGLCommandList::DrawIndexedInstanced(PrimitiveType type, unsigned int indexCount, unsigned int instanceCount, unsigned int startIndex, int baseVertex, unsigned int startInstance) {
m_primitiveType = ToOpenGL(type);
if (m_currentVAO == 0) {
EnsureInternalVertexArrayBound();
}
const uint64_t indexOffset = m_currentIndexOffset + static_cast<uint64_t>(startIndex) * GetIndexTypeSize(m_currentIndexType);
glDrawElementsInstanced(m_primitiveType,
indexCount,
@@ -1055,6 +1067,9 @@ void OpenGLCommandList::CopyResource(RHIResourceView* dst, RHIResourceView* src)
}
void OpenGLCommandList::Draw(uint32_t vertexCount, uint32_t instanceCount, uint32_t startVertex, uint32_t startInstance) {
if (m_currentVAO == 0) {
EnsureInternalVertexArrayBound();
}
glDrawArraysInstanced(m_primitiveType,
static_cast<GLint>(startVertex),
static_cast<GLsizei>(vertexCount),
@@ -1063,6 +1078,9 @@ void OpenGLCommandList::Draw(uint32_t vertexCount, uint32_t instanceCount, uint3
}
void OpenGLCommandList::DrawIndexed(uint32_t indexCount, uint32_t instanceCount, uint32_t startIndex, int32_t baseVertex, uint32_t startInstance) {
if (m_currentVAO == 0) {
EnsureInternalVertexArrayBound();
}
const uint64_t indexOffset = m_currentIndexOffset + static_cast<uint64_t>(startIndex) * GetIndexTypeSize(m_currentIndexType);
glDrawElementsInstanced(m_primitiveType,
static_cast<GLsizei>(indexCount),
@@ -1078,7 +1096,23 @@ void OpenGLCommandList::SetPrimitiveTopology(PrimitiveTopology topology) {
}
void OpenGLCommandList::SetStencilRef(uint8_t ref) {
glStencilFunc(GL_FRONT_AND_BACK, ref, 0xFF);
if (m_currentPipelineState == nullptr) {
glStencilFuncSeparate(GL_FRONT, GL_ALWAYS, ref, 0xFF);
glStencilFuncSeparate(GL_BACK, GL_ALWAYS, ref, 0xFF);
return;
}
const OpenGLDepthStencilState& state = m_currentPipelineState->GetOpenGLDepthStencilState();
glStencilFuncSeparate(
GL_FRONT,
ToOpenGL(state.frontStencilFunc),
ref,
state.stencilReadMask);
glStencilFuncSeparate(
GL_BACK,
ToOpenGL(state.backStencilFunc),
ref,
state.stencilReadMask);
}
void OpenGLCommandList::SetBlendFactor(const float factor[4]) {

View File

@@ -28,7 +28,7 @@ void OpenGLPipelineState::SetRasterizerState(const RasterizerDesc& state) {
m_glRasterizerState.scissorTestEnable = state.scissorTestEnable;
m_glRasterizerState.multisampleEnable = state.multisampleEnable;
m_glRasterizerState.polygonOffsetFactor = state.slopeScaledDepthBias;
m_glRasterizerState.polygonOffsetUnits = state.depthBiasClamp;
m_glRasterizerState.polygonOffsetUnits = static_cast<float>(state.depthBias);
}
void OpenGLPipelineState::SetBlendState(const BlendDesc& state) {
@@ -52,10 +52,14 @@ void OpenGLPipelineState::SetDepthStencilState(const DepthStencilStateDesc& stat
m_glDepthStencilState.stencilEnable = state.stencilEnable;
m_glDepthStencilState.stencilReadMask = state.stencilReadMask;
m_glDepthStencilState.stencilWriteMask = state.stencilWriteMask;
m_glDepthStencilState.stencilFunc = static_cast<ComparisonFunc>(state.front.func);
m_glDepthStencilState.stencilFailOp = static_cast<StencilOp>(state.front.failOp);
m_glDepthStencilState.stencilDepthFailOp = static_cast<StencilOp>(state.front.depthFailOp);
m_glDepthStencilState.stencilDepthPassOp = static_cast<StencilOp>(state.front.passOp);
m_glDepthStencilState.frontStencilFunc = static_cast<ComparisonFunc>(state.front.func);
m_glDepthStencilState.frontStencilFailOp = static_cast<StencilOp>(state.front.failOp);
m_glDepthStencilState.frontStencilDepthFailOp = static_cast<StencilOp>(state.front.depthFailOp);
m_glDepthStencilState.frontStencilDepthPassOp = static_cast<StencilOp>(state.front.passOp);
m_glDepthStencilState.backStencilFunc = static_cast<ComparisonFunc>(state.back.func);
m_glDepthStencilState.backStencilFailOp = static_cast<StencilOp>(state.back.failOp);
m_glDepthStencilState.backStencilDepthFailOp = static_cast<StencilOp>(state.back.depthFailOp);
m_glDepthStencilState.backStencilDepthPassOp = static_cast<StencilOp>(state.back.passOp);
}
void OpenGLPipelineState::SetTopology(uint32_t topologyType) {
@@ -121,16 +125,31 @@ void OpenGLPipelineState::ApplyDepthStencil() {
if (m_glDepthStencilState.stencilEnable) {
glEnable(GL_STENCIL_TEST);
glStencilMask(m_glDepthStencilState.stencilWriteMask);
glStencilFunc(
ToOpenGL(m_glDepthStencilState.stencilFunc),
glStencilMaskSeparate(GL_FRONT, m_glDepthStencilState.stencilWriteMask);
glStencilMaskSeparate(GL_BACK, m_glDepthStencilState.stencilWriteMask);
glStencilFuncSeparate(
GL_FRONT,
ToOpenGL(m_glDepthStencilState.frontStencilFunc),
m_glDepthStencilState.stencilRef,
m_glDepthStencilState.stencilReadMask
);
glStencilOp(
ToOpenGL(m_glDepthStencilState.stencilFailOp),
ToOpenGL(m_glDepthStencilState.stencilDepthFailOp),
ToOpenGL(m_glDepthStencilState.stencilDepthPassOp)
glStencilOpSeparate(
GL_FRONT,
ToOpenGL(m_glDepthStencilState.frontStencilFailOp),
ToOpenGL(m_glDepthStencilState.frontStencilDepthFailOp),
ToOpenGL(m_glDepthStencilState.frontStencilDepthPassOp)
);
glStencilFuncSeparate(
GL_BACK,
ToOpenGL(m_glDepthStencilState.backStencilFunc),
m_glDepthStencilState.stencilRef,
m_glDepthStencilState.stencilReadMask
);
glStencilOpSeparate(
GL_BACK,
ToOpenGL(m_glDepthStencilState.backStencilFailOp),
ToOpenGL(m_glDepthStencilState.backStencilDepthFailOp),
ToOpenGL(m_glDepthStencilState.backStencilDepthPassOp)
);
} else {
glDisable(GL_STENCIL_TEST);
@@ -177,6 +196,18 @@ void OpenGLPipelineState::ApplyRasterizer() {
} else {
glDisable(GL_MULTISAMPLE);
}
const bool polygonOffsetEnable =
m_glRasterizerState.polygonOffsetFactor != 0.0f ||
m_glRasterizerState.polygonOffsetUnits != 0.0f;
if (polygonOffsetEnable) {
glEnable(GL_POLYGON_OFFSET_FILL);
glPolygonOffset(
m_glRasterizerState.polygonOffsetFactor,
m_glRasterizerState.polygonOffsetUnits);
} else {
glDisable(GL_POLYGON_OFFSET_FILL);
}
}
void OpenGLPipelineState::ApplyViewport() {

View File

@@ -305,6 +305,7 @@ void VulkanCommandList::Reset() {
m_renderPassActive = false;
m_hasViewport = false;
m_hasScissor = false;
m_stencilRef = 0;
VkCommandBufferBeginInfo beginInfo = {};
beginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO;
@@ -586,7 +587,10 @@ void VulkanCommandList::SetRenderTargets(uint32_t count, RHIResourceView** rende
}
void VulkanCommandList::SetStencilRef(uint8_t ref) {
(void)ref;
m_stencilRef = ref;
if (m_renderPassActive && m_currentPipelineState != nullptr) {
vkCmdSetStencilReference(m_commandBuffer, VK_STENCIL_FRONT_AND_BACK, m_stencilRef);
}
}
void VulkanCommandList::SetBlendFactor(const float factor[4]) {
@@ -643,6 +647,9 @@ void VulkanCommandList::Draw(uint32_t vertexCount, uint32_t instanceCount, uint3
}
vkCmdBindPipeline(m_commandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, m_currentPipelineState->GetPipeline());
if (m_currentPipelineState->GetDepthStencilState().stencilEnable) {
vkCmdSetStencilReference(m_commandBuffer, VK_STENCIL_FRONT_AND_BACK, m_stencilRef);
}
vkCmdDraw(m_commandBuffer, vertexCount, instanceCount, startVertex, startInstance);
}
@@ -652,6 +659,9 @@ void VulkanCommandList::DrawIndexed(uint32_t indexCount, uint32_t instanceCount,
}
vkCmdBindPipeline(m_commandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, m_currentPipelineState->GetPipeline());
if (m_currentPipelineState->GetDepthStencilState().stencilEnable) {
vkCmdSetStencilReference(m_commandBuffer, VK_STENCIL_FRONT_AND_BACK, m_stencilRef);
}
vkCmdDrawIndexed(m_commandBuffer, indexCount, instanceCount, startIndex, baseVertex, startInstance);
}

View File

@@ -34,6 +34,40 @@ VkShaderModule CreateShaderModule(VkDevice device, const std::vector<uint32_t>&
return module;
}
VkStencilOp ToVulkanStencilOp(StencilOp op) {
switch (op) {
case StencilOp::Zero:
return VK_STENCIL_OP_ZERO;
case StencilOp::Replace:
return VK_STENCIL_OP_REPLACE;
case StencilOp::IncrSat:
return VK_STENCIL_OP_INCREMENT_AND_CLAMP;
case StencilOp::DecrSat:
return VK_STENCIL_OP_DECREMENT_AND_CLAMP;
case StencilOp::Invert:
return VK_STENCIL_OP_INVERT;
case StencilOp::Incr:
return VK_STENCIL_OP_INCREMENT_AND_WRAP;
case StencilOp::Decr:
return VK_STENCIL_OP_DECREMENT_AND_WRAP;
case StencilOp::Keep:
default:
return VK_STENCIL_OP_KEEP;
}
}
VkStencilOpState BuildVulkanStencilOpState(const StencilOpDesc& desc) {
VkStencilOpState state = {};
state.failOp = ToVulkanStencilOp(static_cast<StencilOp>(desc.failOp));
state.passOp = ToVulkanStencilOp(static_cast<StencilOp>(desc.passOp));
state.depthFailOp = ToVulkanStencilOp(static_cast<StencilOp>(desc.depthFailOp));
state.compareOp = ToVulkanCompareOp(static_cast<ComparisonFunc>(desc.func));
state.compareMask = 0xFFu;
state.writeMask = 0xFFu;
state.reference = 0u;
return state;
}
} // namespace
VulkanPipelineState::~VulkanPipelineState() {
@@ -303,10 +337,17 @@ bool VulkanPipelineState::CreateGraphicsPipeline(const GraphicsPipelineDesc& des
depthStencil.depthCompareOp = ToVulkanCompareOp(static_cast<ComparisonFunc>(m_depthStencilDesc.depthFunc));
depthStencil.depthBoundsTestEnable = m_depthStencilDesc.depthBoundsEnable ? VK_TRUE : VK_FALSE;
depthStencil.stencilTestEnable = m_depthStencilDesc.stencilEnable ? VK_TRUE : VK_FALSE;
depthStencil.front = BuildVulkanStencilOpState(m_depthStencilDesc.front);
depthStencil.back = BuildVulkanStencilOpState(m_depthStencilDesc.back);
depthStencil.front.compareMask = m_depthStencilDesc.stencilReadMask;
depthStencil.front.writeMask = m_depthStencilDesc.stencilWriteMask;
depthStencil.back.compareMask = m_depthStencilDesc.stencilReadMask;
depthStencil.back.writeMask = m_depthStencilDesc.stencilWriteMask;
VkDynamicState dynamicStates[] = {
VK_DYNAMIC_STATE_VIEWPORT,
VK_DYNAMIC_STATE_SCISSOR
VK_DYNAMIC_STATE_SCISSOR,
VK_DYNAMIC_STATE_STENCIL_REFERENCE
};
VkPipelineDynamicStateCreateInfo dynamicState = {};
dynamicState.sType = VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO;

View File

@@ -75,14 +75,27 @@ RHI::GraphicsPipelineDesc CreatePipelineDesc(
pipelineDesc.depthStencilState.depthWriteEnable = false;
pipelineDesc.depthStencilState.depthFunc = static_cast<uint32_t>(RHI::ComparisonFunc::Always);
const Resources::ShaderPass* shaderPass = shader.FindPass(passName);
const Resources::ShaderBackend backend = ::XCEngine::Rendering::Detail::ToShaderBackend(backendType);
if (const Resources::ShaderStageVariant* vertexVariant =
shader.FindVariant(passName, Resources::ShaderType::Vertex, backend)) {
::XCEngine::Rendering::Detail::ApplyShaderStageVariant(*vertexVariant, pipelineDesc.vertexShader);
if (shaderPass != nullptr) {
::XCEngine::Rendering::Detail::ApplyShaderStageVariant(
*shaderPass,
backend,
*vertexVariant,
pipelineDesc.vertexShader);
}
}
if (const Resources::ShaderStageVariant* fragmentVariant =
shader.FindVariant(passName, Resources::ShaderType::Fragment, backend)) {
::XCEngine::Rendering::Detail::ApplyShaderStageVariant(*fragmentVariant, pipelineDesc.fragmentShader);
if (shaderPass != nullptr) {
::XCEngine::Rendering::Detail::ApplyShaderStageVariant(
*shaderPass,
backend,
*fragmentVariant,
pipelineDesc.fragmentShader);
}
}
return pipelineDesc;

View File

@@ -77,14 +77,27 @@ RHI::GraphicsPipelineDesc CreatePipelineDesc(
pipelineDesc.depthStencilState.depthWriteEnable = false;
pipelineDesc.depthStencilState.depthFunc = static_cast<uint32_t>(RHI::ComparisonFunc::Always);
const Resources::ShaderPass* shaderPass = shader.FindPass(passName);
const Resources::ShaderBackend backend = ::XCEngine::Rendering::Detail::ToShaderBackend(backendType);
if (const Resources::ShaderStageVariant* vertexVariant =
shader.FindVariant(passName, Resources::ShaderType::Vertex, backend)) {
::XCEngine::Rendering::Detail::ApplyShaderStageVariant(*vertexVariant, pipelineDesc.vertexShader);
if (shaderPass != nullptr) {
::XCEngine::Rendering::Detail::ApplyShaderStageVariant(
*shaderPass,
backend,
*vertexVariant,
pipelineDesc.vertexShader);
}
}
if (const Resources::ShaderStageVariant* fragmentVariant =
shader.FindVariant(passName, Resources::ShaderType::Fragment, backend)) {
::XCEngine::Rendering::Detail::ApplyShaderStageVariant(*fragmentVariant, pipelineDesc.fragmentShader);
if (shaderPass != nullptr) {
::XCEngine::Rendering::Detail::ApplyShaderStageVariant(
*shaderPass,
backend,
*fragmentVariant,
pipelineDesc.fragmentShader);
}
}
return pipelineDesc;

View File

@@ -7,6 +7,7 @@
#include <XCEngine/RHI/RHICommandList.h>
#include <XCEngine/RHI/RHIDevice.h>
#include "Rendering/Materials/RenderMaterialStateUtils.h"
#include "Rendering/Detail/ShaderVariantUtils.h"
#include <algorithm>
@@ -332,35 +333,50 @@ bool BuiltinInfiniteGridPass::CreateResources(const RenderContext& renderContext
pipelineDesc.depthStencilFormat = static_cast<uint32_t>(RHI::Format::D24_UNorm_S8_UInt);
pipelineDesc.sampleCount = 1;
pipelineDesc.rasterizerState.fillMode = static_cast<uint32_t>(RHI::FillMode::Solid);
pipelineDesc.rasterizerState.cullMode = static_cast<uint32_t>(RHI::CullMode::None);
pipelineDesc.rasterizerState.frontFace = static_cast<uint32_t>(RHI::FrontFace::CounterClockwise);
pipelineDesc.rasterizerState.depthClipEnable = true;
pipelineDesc.blendState.blendEnable = true;
pipelineDesc.blendState.srcBlend = static_cast<uint32_t>(RHI::BlendFactor::SrcAlpha);
pipelineDesc.blendState.dstBlend = static_cast<uint32_t>(RHI::BlendFactor::InvSrcAlpha);
pipelineDesc.blendState.srcBlendAlpha = static_cast<uint32_t>(RHI::BlendFactor::One);
pipelineDesc.blendState.dstBlendAlpha = static_cast<uint32_t>(RHI::BlendFactor::InvSrcAlpha);
pipelineDesc.blendState.blendOp = static_cast<uint32_t>(RHI::BlendOp::Add);
pipelineDesc.blendState.blendOpAlpha = static_cast<uint32_t>(RHI::BlendOp::Add);
pipelineDesc.blendState.colorWriteMask = 0xF;
pipelineDesc.depthStencilState.depthTestEnable = true;
pipelineDesc.depthStencilState.depthWriteEnable = false;
pipelineDesc.depthStencilState.depthFunc = static_cast<uint32_t>(RHI::ComparisonFunc::LessEqual);
if (infiniteGridPass->hasFixedFunctionState) {
::XCEngine::Rendering::ApplyRenderState(infiniteGridPass->fixedFunctionState, pipelineDesc);
} else {
Resources::MaterialRenderState fallbackState = {};
fallbackState.cullMode = Resources::MaterialCullMode::None;
fallbackState.depthWriteEnable = false;
fallbackState.depthTestEnable = true;
fallbackState.depthFunc = Resources::MaterialComparisonFunc::LessEqual;
fallbackState.blendEnable = true;
fallbackState.srcBlend = Resources::MaterialBlendFactor::SrcAlpha;
fallbackState.dstBlend = Resources::MaterialBlendFactor::InvSrcAlpha;
fallbackState.srcBlendAlpha = Resources::MaterialBlendFactor::One;
fallbackState.dstBlendAlpha = Resources::MaterialBlendFactor::InvSrcAlpha;
fallbackState.blendOp = Resources::MaterialBlendOp::Add;
fallbackState.blendOpAlpha = Resources::MaterialBlendOp::Add;
fallbackState.colorWriteMask = static_cast<uint8_t>(RHI::ColorWriteMask::All);
::XCEngine::Rendering::ApplyRenderState(fallbackState, pipelineDesc);
}
if (const Resources::ShaderStageVariant* vertexVariant = m_builtinInfiniteGridShader->FindVariant(
infiniteGridPass->name,
Resources::ShaderType::Vertex,
backend)) {
::XCEngine::Rendering::Detail::ApplyShaderStageVariant(*vertexVariant, pipelineDesc.vertexShader);
const Resources::ShaderPass* shaderPass = m_builtinInfiniteGridShader->FindPass(infiniteGridPass->name);
if (shaderPass != nullptr) {
::XCEngine::Rendering::Detail::ApplyShaderStageVariant(
*shaderPass,
backend,
*vertexVariant,
pipelineDesc.vertexShader);
}
}
if (const Resources::ShaderStageVariant* fragmentVariant = m_builtinInfiniteGridShader->FindVariant(
infiniteGridPass->name,
Resources::ShaderType::Fragment,
backend)) {
::XCEngine::Rendering::Detail::ApplyShaderStageVariant(*fragmentVariant, pipelineDesc.fragmentShader);
const Resources::ShaderPass* shaderPass = m_builtinInfiniteGridShader->FindPass(infiniteGridPass->name);
if (shaderPass != nullptr) {
::XCEngine::Rendering::Detail::ApplyShaderStageVariant(
*shaderPass,
backend,
*fragmentVariant,
pipelineDesc.fragmentShader);
}
}
m_pipelineState = m_device->CreatePipelineState(pipelineDesc);

View File

@@ -4,8 +4,10 @@
#include "Debug/Logger.h"
#include "Rendering/Picking/ObjectIdCodec.h"
#include "Rendering/Detail/ShaderVariantUtils.h"
#include "Rendering/Materials/RenderMaterialStateUtils.h"
#include "RHI/RHICommandList.h"
#include "RHI/RHIDevice.h"
#include <XCEngine/Resources/BuiltinResources.h>
#include <algorithm>
#include <utility>
@@ -52,32 +54,46 @@ RHI::GraphicsPipelineDesc CreatePipelineDesc(
pipelineDesc.depthStencilFormat = static_cast<uint32_t>(RHI::Format::Unknown);
pipelineDesc.sampleCount = 1;
pipelineDesc.rasterizerState.fillMode = static_cast<uint32_t>(RHI::FillMode::Solid);
pipelineDesc.rasterizerState.cullMode = static_cast<uint32_t>(RHI::CullMode::None);
pipelineDesc.rasterizerState.frontFace = static_cast<uint32_t>(RHI::FrontFace::CounterClockwise);
pipelineDesc.rasterizerState.depthClipEnable = true;
pipelineDesc.blendState.blendEnable = true;
pipelineDesc.blendState.srcBlend = static_cast<uint32_t>(RHI::BlendFactor::SrcAlpha);
pipelineDesc.blendState.dstBlend = static_cast<uint32_t>(RHI::BlendFactor::InvSrcAlpha);
pipelineDesc.blendState.srcBlendAlpha = static_cast<uint32_t>(RHI::BlendFactor::One);
pipelineDesc.blendState.dstBlendAlpha = static_cast<uint32_t>(RHI::BlendFactor::InvSrcAlpha);
pipelineDesc.blendState.blendOp = static_cast<uint32_t>(RHI::BlendOp::Add);
pipelineDesc.blendState.blendOpAlpha = static_cast<uint32_t>(RHI::BlendOp::Add);
pipelineDesc.blendState.colorWriteMask = static_cast<uint8_t>(RHI::ColorWriteMask::All);
pipelineDesc.depthStencilState.depthTestEnable = false;
pipelineDesc.depthStencilState.depthWriteEnable = false;
pipelineDesc.depthStencilState.depthFunc = static_cast<uint32_t>(RHI::ComparisonFunc::Always);
const Resources::ShaderPass* shaderPass = shader.FindPass(passName);
if (shaderPass != nullptr && shaderPass->hasFixedFunctionState) {
::XCEngine::Rendering::ApplyRenderState(shaderPass->fixedFunctionState, pipelineDesc);
} else {
Resources::MaterialRenderState fallbackState = {};
fallbackState.cullMode = Resources::MaterialCullMode::None;
fallbackState.depthWriteEnable = false;
fallbackState.depthTestEnable = false;
fallbackState.depthFunc = Resources::MaterialComparisonFunc::Always;
fallbackState.blendEnable = true;
fallbackState.srcBlend = Resources::MaterialBlendFactor::SrcAlpha;
fallbackState.dstBlend = Resources::MaterialBlendFactor::InvSrcAlpha;
fallbackState.srcBlendAlpha = Resources::MaterialBlendFactor::One;
fallbackState.dstBlendAlpha = Resources::MaterialBlendFactor::InvSrcAlpha;
fallbackState.blendOp = Resources::MaterialBlendOp::Add;
fallbackState.blendOpAlpha = Resources::MaterialBlendOp::Add;
fallbackState.colorWriteMask = static_cast<uint8_t>(RHI::ColorWriteMask::All);
::XCEngine::Rendering::ApplyRenderState(fallbackState, pipelineDesc);
}
const Resources::ShaderBackend backend = ::XCEngine::Rendering::Detail::ToShaderBackend(backendType);
if (const Resources::ShaderStageVariant* vertexVariant =
shader.FindVariant(passName, Resources::ShaderType::Vertex, backend)) {
::XCEngine::Rendering::Detail::ApplyShaderStageVariant(*vertexVariant, pipelineDesc.vertexShader);
if (shaderPass != nullptr) {
::XCEngine::Rendering::Detail::ApplyShaderStageVariant(
*shaderPass,
backend,
*vertexVariant,
pipelineDesc.vertexShader);
}
}
if (const Resources::ShaderStageVariant* fragmentVariant =
shader.FindVariant(passName, Resources::ShaderType::Fragment, backend)) {
::XCEngine::Rendering::Detail::ApplyShaderStageVariant(*fragmentVariant, pipelineDesc.fragmentShader);
if (shaderPass != nullptr) {
::XCEngine::Rendering::Detail::ApplyShaderStageVariant(
*shaderPass,
backend,
*fragmentVariant,
pipelineDesc.fragmentShader);
}
}
return pipelineDesc;
@@ -86,7 +102,7 @@ RHI::GraphicsPipelineDesc CreatePipelineDesc(
} // namespace
BuiltinObjectIdOutlinePass::BuiltinObjectIdOutlinePass(Containers::String shaderPath)
: m_shaderPath(std::move(shaderPath)) {
: m_shaderPath(shaderPath.Empty() ? Resources::GetBuiltinObjectIdOutlineShaderPath() : std::move(shaderPath)) {
ResetState();
}

View File

@@ -77,14 +77,27 @@ RHI::GraphicsPipelineDesc CreatePipelineDesc(
pipelineDesc.depthStencilState.depthWriteEnable = true;
pipelineDesc.depthStencilState.depthFunc = static_cast<uint32_t>(RHI::ComparisonFunc::LessEqual);
const Resources::ShaderPass* shaderPass = shader.FindPass(passName);
const Resources::ShaderBackend backend = ::XCEngine::Rendering::Detail::ToShaderBackend(backendType);
if (const Resources::ShaderStageVariant* vertexVariant =
shader.FindVariant(passName, Resources::ShaderType::Vertex, backend)) {
::XCEngine::Rendering::Detail::ApplyShaderStageVariant(*vertexVariant, pipelineDesc.vertexShader);
if (shaderPass != nullptr) {
::XCEngine::Rendering::Detail::ApplyShaderStageVariant(
*shaderPass,
backend,
*vertexVariant,
pipelineDesc.vertexShader);
}
}
if (const Resources::ShaderStageVariant* fragmentVariant =
shader.FindVariant(passName, Resources::ShaderType::Fragment, backend)) {
::XCEngine::Rendering::Detail::ApplyShaderStageVariant(*fragmentVariant, pipelineDesc.fragmentShader);
if (shaderPass != nullptr) {
::XCEngine::Rendering::Detail::ApplyShaderStageVariant(
*shaderPass,
backend,
*fragmentVariant,
pipelineDesc.fragmentShader);
}
}
return pipelineDesc;

View File

@@ -146,14 +146,27 @@ RHI::GraphicsPipelineDesc CreateSkyboxPipelineDesc(
pipelineDesc.depthStencilState.depthWriteEnable = false;
pipelineDesc.depthStencilState.depthFunc = static_cast<uint32_t>(RHI::ComparisonFunc::LessEqual);
const Resources::ShaderPass* shaderPass = shader.FindPass(passName);
const Resources::ShaderBackend backend = ::XCEngine::Rendering::Detail::ToShaderBackend(backendType);
if (const Resources::ShaderStageVariant* vertexVariant =
shader.FindVariant(passName, Resources::ShaderType::Vertex, backend)) {
::XCEngine::Rendering::Detail::ApplyShaderStageVariant(*vertexVariant, pipelineDesc.vertexShader);
if (shaderPass != nullptr) {
::XCEngine::Rendering::Detail::ApplyShaderStageVariant(
*shaderPass,
backend,
*vertexVariant,
pipelineDesc.vertexShader);
}
}
if (const Resources::ShaderStageVariant* fragmentVariant =
shader.FindVariant(passName, Resources::ShaderType::Fragment, backend)) {
::XCEngine::Rendering::Detail::ApplyShaderStageVariant(*fragmentVariant, pipelineDesc.fragmentShader);
if (shaderPass != nullptr) {
::XCEngine::Rendering::Detail::ApplyShaderStageVariant(
*shaderPass,
backend,
*fragmentVariant,
pipelineDesc.fragmentShader);
}
}
return pipelineDesc;

View File

@@ -324,7 +324,6 @@ void Material::Release() {
m_renderQueue = static_cast<Core::int32>(MaterialRenderQueue::Geometry);
m_renderState = MaterialRenderState();
m_hasRenderStateOverride = false;
m_legacyShaderPassHint.Clear();
m_tags.Clear();
m_keywordSet.enabledKeywords.Clear();
m_properties.Clear();
@@ -363,24 +362,6 @@ void Material::SetRenderStateOverrideEnabled(bool enabled) {
MarkChanged(false);
}
void Material::SetLegacyShaderPassHint(const Containers::String& shaderPass) {
m_legacyShaderPassHint = shaderPass;
MarkChanged(false);
}
void Material::ClearLegacyShaderPassHint() {
if (m_legacyShaderPassHint.Empty()) {
return;
}
m_legacyShaderPassHint.Clear();
MarkChanged(false);
}
void Material::SetShaderPass(const Containers::String& shaderPass) {
SetLegacyShaderPassHint(shaderPass);
}
void Material::SetTag(const Containers::String& name, const Containers::String& value) {
for (MaterialTagEntry& tag : m_tags) {
if (tag.name == name) {
@@ -1120,7 +1101,6 @@ void Material::UpdateMemorySize() {
m_memorySize = m_constantBufferData.Size() +
m_constantLayout.Size() * sizeof(MaterialConstantFieldDesc) +
sizeof(MaterialRenderState) +
m_legacyShaderPassHint.Length() +
m_tags.Size() * sizeof(MaterialTagEntry) +
m_keywordSet.enabledKeywords.Size() * sizeof(Containers::String) +
m_textureBindings.Size() * sizeof(MaterialTextureBinding) +

View File

@@ -42,10 +42,25 @@ bool TryParseAuthoringBoolDirectiveToken(const std::string& token, bool& outValu
bool TryParseAuthoringCullMode(const std::string& token, MaterialCullMode& outMode);
bool TryParseAuthoringComparisonFunc(const std::string& token, MaterialComparisonFunc& outFunc);
bool TryParseAuthoringBlendFactor(const std::string& token, MaterialBlendFactor& outFactor);
bool TryParseAuthoringBlendOp(const std::string& token, MaterialBlendOp& outOp);
bool TryParseAuthoringStencilOp(const std::string& token, MaterialStencilOp& outOp);
bool TryParseAuthoringColorMask(const std::string& token, Core::uint8& outMask);
bool TryParseAuthoringBlendDirective(
const std::vector<std::string>& tokens,
MaterialRenderState& outState);
bool TryParseAuthoringBlendOpDirective(
const std::vector<std::string>& tokens,
MaterialRenderState& outState);
bool TryParseAuthoringOffsetDirective(
const std::vector<std::string>& tokens,
MaterialRenderState& outState);
bool TryParseAuthoringStencilDirective(
const std::vector<std::string>& tokens,
MaterialStencilState& outState);
bool TryParseAuthoringUsePassReference(
const Containers::String& reference,
Containers::String& outShaderName,
Containers::String& outPassName);
void SetOrReplaceAuthoringTag(
std::vector<ShaderTagIR>& tags,

View File

@@ -1,518 +0,0 @@
#include "ShaderManifestLoader.h"
#include "ShaderFileUtils.h"
#include "ShaderRuntimeBuildUtils.h"
#include "../ShaderSourceUtils.h"
#include <cctype>
#include <filesystem>
#include <functional>
#include <memory>
#include <unordered_set>
#include <vector>
namespace XCEngine {
namespace Resources {
namespace {
bool FindValueStart(const std::string& json, const char* key, size_t& valuePos) {
const std::string token = std::string("\"") + key + "\"";
const size_t keyPos = json.find(token);
if (keyPos == std::string::npos) {
return false;
}
const size_t colonPos = json.find(':', keyPos + token.length());
if (colonPos == std::string::npos) {
return false;
}
valuePos = SkipWhitespace(json, colonPos + 1);
return valuePos < json.size();
}
bool TryParseStringValue(const std::string& json, const char* key, Containers::String& outValue) {
size_t valuePos = 0;
if (!FindValueStart(json, key, valuePos)) {
return false;
}
return ParseQuotedString(json, valuePos, outValue);
}
bool TryExtractDelimitedValue(
const std::string& json,
const char* key,
char openChar,
char closeChar,
std::string& outValue) {
size_t valuePos = 0;
if (!FindValueStart(json, key, valuePos) || valuePos >= json.size() || json[valuePos] != openChar) {
return false;
}
bool inString = false;
bool escaped = false;
int depth = 0;
for (size_t pos = valuePos; pos < json.size(); ++pos) {
const char ch = json[pos];
if (escaped) {
escaped = false;
continue;
}
if (ch == '\\') {
escaped = true;
continue;
}
if (ch == '"') {
inString = !inString;
continue;
}
if (inString) {
continue;
}
if (ch == openChar) {
++depth;
} else if (ch == closeChar) {
--depth;
if (depth == 0) {
outValue = json.substr(valuePos, pos - valuePos + 1);
return true;
}
}
}
return false;
}
bool TryExtractObject(const std::string& json, const char* key, std::string& outObject) {
return TryExtractDelimitedValue(json, key, '{', '}', outObject);
}
bool TryExtractArray(const std::string& json, const char* key, std::string& outArray) {
return TryExtractDelimitedValue(json, key, '[', ']', outArray);
}
bool TryParseStringMapObject(
const std::string& objectText,
const std::function<void(const Containers::String&, const Containers::String&)>& onEntry) {
if (!onEntry || objectText.empty() || objectText.front() != '{' || objectText.back() != '}') {
return false;
}
size_t pos = 1;
while (pos < objectText.size()) {
pos = SkipWhitespace(objectText, pos);
if (pos >= objectText.size()) {
return false;
}
if (objectText[pos] == '}') {
return true;
}
Containers::String key;
if (!ParseQuotedString(objectText, pos, key, &pos)) {
return false;
}
pos = SkipWhitespace(objectText, pos);
if (pos >= objectText.size() || objectText[pos] != ':') {
return false;
}
pos = SkipWhitespace(objectText, pos + 1);
Containers::String value;
if (!ParseQuotedString(objectText, pos, value, &pos)) {
return false;
}
onEntry(key, value);
pos = SkipWhitespace(objectText, pos);
if (pos >= objectText.size()) {
return false;
}
if (objectText[pos] == ',') {
++pos;
continue;
}
if (objectText[pos] == '}') {
return true;
}
return false;
}
return false;
}
bool SplitTopLevelArrayElements(const std::string& arrayText, std::vector<std::string>& outElements) {
outElements.clear();
if (arrayText.size() < 2 || arrayText.front() != '[' || arrayText.back() != ']') {
return false;
}
bool inString = false;
bool escaped = false;
int objectDepth = 0;
int arrayDepth = 0;
size_t elementStart = std::string::npos;
for (size_t pos = 1; pos + 1 < arrayText.size(); ++pos) {
const char ch = arrayText[pos];
if (escaped) {
escaped = false;
continue;
}
if (ch == '\\') {
escaped = true;
continue;
}
if (ch == '"') {
if (elementStart == std::string::npos) {
elementStart = pos;
}
inString = !inString;
continue;
}
if (inString) {
continue;
}
if (std::isspace(static_cast<unsigned char>(ch)) != 0) {
continue;
}
if (elementStart == std::string::npos) {
elementStart = pos;
}
if (ch == '{') {
++objectDepth;
} else if (ch == '}') {
--objectDepth;
} else if (ch == '[') {
++arrayDepth;
} else if (ch == ']') {
--arrayDepth;
} else if (ch == ',' && objectDepth == 0 && arrayDepth == 0) {
outElements.push_back(TrimCopy(arrayText.substr(elementStart, pos - elementStart)));
elementStart = std::string::npos;
}
}
if (elementStart != std::string::npos) {
outElements.push_back(TrimCopy(arrayText.substr(elementStart, arrayText.size() - 1 - elementStart)));
}
return true;
}
bool TryParseShaderKeywordsArray(
const std::string& arrayText,
ShaderKeywordSet& outKeywordSet) {
outKeywordSet = {};
std::vector<std::string> keywordElements;
if (!SplitTopLevelArrayElements(arrayText, keywordElements)) {
return false;
}
for (const std::string& keywordElement : keywordElements) {
if (keywordElement.empty()) {
continue;
}
Containers::String keyword;
size_t nextPos = 0;
if (!ParseQuotedString(keywordElement, 0, keyword, &nextPos)) {
return false;
}
if (SkipWhitespace(keywordElement, nextPos) != keywordElement.size()) {
return false;
}
outKeywordSet.enabledKeywords.PushBack(keyword);
}
NormalizeShaderKeywordSetInPlace(outKeywordSet);
return true;
}
bool TryParseUnsignedValue(const std::string& json, const char* key, Core::uint32& outValue) {
size_t valuePos = 0;
if (!FindValueStart(json, key, valuePos)) {
return false;
}
size_t endPos = valuePos;
while (endPos < json.size() && std::isdigit(static_cast<unsigned char>(json[endPos])) != 0) {
++endPos;
}
if (endPos == valuePos) {
return false;
}
try {
outValue = static_cast<Core::uint32>(std::stoul(json.substr(valuePos, endPos - valuePos)));
return true;
} catch (...) {
return false;
}
}
} // namespace
bool LooksLikeShaderManifest(const std::string& sourceText) {
const size_t firstContentPos = SkipWhitespace(sourceText, 0);
return firstContentPos < sourceText.size() &&
sourceText[firstContentPos] == '{' &&
sourceText.find("\"passes\"") != std::string::npos;
}
bool CollectShaderManifestDependencyPaths(
const Containers::String& path,
const std::string& jsonText,
Containers::Array<Containers::String>& outDependencies) {
outDependencies.Clear();
std::string passesArray;
if (!TryExtractArray(jsonText, "passes", passesArray)) {
return false;
}
std::vector<std::string> passObjects;
if (!SplitTopLevelArrayElements(passesArray, passObjects)) {
return false;
}
std::unordered_set<std::string> seenPaths;
for (const std::string& passObject : passObjects) {
std::string variantsArray;
if (!TryExtractArray(passObject, "variants", variantsArray)) {
return false;
}
std::vector<std::string> variantObjects;
if (!SplitTopLevelArrayElements(variantsArray, variantObjects)) {
return false;
}
for (const std::string& variantObject : variantObjects) {
Containers::String sourcePath;
if (!TryParseStringValue(variantObject, "source", sourcePath) &&
!TryParseStringValue(variantObject, "sourcePath", sourcePath)) {
continue;
}
const Containers::String resolvedPath = ResolveShaderDependencyPath(sourcePath, path);
const std::string key = ToStdString(resolvedPath);
if (!key.empty() && seenPaths.insert(key).second) {
outDependencies.PushBack(resolvedPath);
}
}
}
return true;
}
LoadResult LoadShaderManifest(
const Containers::String& path,
const std::string& jsonText) {
std::string passesArray;
if (!TryExtractArray(jsonText, "passes", passesArray)) {
return LoadResult("Shader manifest is missing a valid passes array: " + path);
}
std::vector<std::string> passObjects;
if (!SplitTopLevelArrayElements(passesArray, passObjects) || passObjects.empty()) {
return LoadResult("Shader manifest does not contain any pass objects: " + path);
}
auto shader = std::make_unique<Shader>();
IResource::ConstructParams params;
params.path = path;
params.guid = ResourceGUID::Generate(path);
Containers::String manifestName;
if (TryParseStringValue(jsonText, "name", manifestName) && !manifestName.Empty()) {
params.name = manifestName;
} else {
const std::filesystem::path shaderPath(path.CStr());
const std::string stem = shaderPath.stem().generic_string();
params.name = stem.empty() ? path : Containers::String(stem.c_str());
}
shader->Initialize(params);
Containers::String fallback;
if (TryParseStringValue(jsonText, "fallback", fallback)) {
shader->SetFallback(fallback);
}
std::string propertiesArray;
if (TryExtractArray(jsonText, "properties", propertiesArray)) {
std::vector<std::string> propertyObjects;
if (!SplitTopLevelArrayElements(propertiesArray, propertyObjects)) {
return LoadResult("Shader manifest properties array could not be parsed: " + path);
}
for (const std::string& propertyObject : propertyObjects) {
ShaderPropertyDesc property = {};
if (!TryParseStringValue(propertyObject, "name", property.name) || property.name.Empty()) {
return LoadResult("Shader manifest property is missing a valid name: " + path);
}
Containers::String propertyTypeName;
if (!TryParseStringValue(propertyObject, "type", propertyTypeName) ||
!TryParseShaderPropertyType(propertyTypeName, property.type)) {
return LoadResult("Shader manifest property has an invalid type: " + path);
}
if (!TryParseStringValue(propertyObject, "displayName", property.displayName)) {
property.displayName = property.name;
}
if (!TryParseStringValue(propertyObject, "defaultValue", property.defaultValue)) {
TryParseStringValue(propertyObject, "default", property.defaultValue);
}
TryParseStringValue(propertyObject, "semantic", property.semantic);
shader->AddProperty(property);
}
}
for (const std::string& passObject : passObjects) {
Containers::String passName;
if (!TryParseStringValue(passObject, "name", passName) || passName.Empty()) {
return LoadResult("Shader manifest pass is missing a valid name: " + path);
}
std::string tagsObject;
if (TryExtractObject(passObject, "tags", tagsObject)) {
if (!TryParseStringMapObject(
tagsObject,
[shaderPtr = shader.get(), &passName](const Containers::String& key, const Containers::String& value) {
shaderPtr->SetPassTag(passName, key, value);
})) {
return LoadResult("Shader manifest pass tags could not be parsed: " + path);
}
}
std::string resourcesArray;
if (TryExtractArray(passObject, "resources", resourcesArray)) {
std::vector<std::string> resourceObjects;
if (!SplitTopLevelArrayElements(resourcesArray, resourceObjects)) {
return LoadResult("Shader manifest pass resources could not be parsed: " + path);
}
for (const std::string& resourceObject : resourceObjects) {
ShaderResourceBindingDesc resourceBinding = {};
if (!TryParseStringValue(resourceObject, "name", resourceBinding.name) ||
resourceBinding.name.Empty()) {
return LoadResult("Shader manifest pass resource is missing a valid name: " + path);
}
Containers::String resourceTypeName;
if (!TryParseStringValue(resourceObject, "type", resourceTypeName) ||
!TryParseShaderResourceType(resourceTypeName, resourceBinding.type)) {
return LoadResult("Shader manifest pass resource has an invalid type: " + path);
}
if (!TryParseUnsignedValue(resourceObject, "set", resourceBinding.set)) {
return LoadResult("Shader manifest pass resource is missing a valid set: " + path);
}
if (!TryParseUnsignedValue(resourceObject, "binding", resourceBinding.binding)) {
return LoadResult("Shader manifest pass resource is missing a valid binding: " + path);
}
TryParseStringValue(resourceObject, "semantic", resourceBinding.semantic);
shader->AddPassResourceBinding(passName, resourceBinding);
}
}
std::string variantsArray;
if (!TryExtractArray(passObject, "variants", variantsArray)) {
return LoadResult("Shader manifest pass is missing variants: " + path);
}
std::vector<std::string> variantObjects;
if (!SplitTopLevelArrayElements(variantsArray, variantObjects) || variantObjects.empty()) {
return LoadResult("Shader manifest pass does not contain any variants: " + path);
}
for (const std::string& variantObject : variantObjects) {
ShaderStageVariant variant = {};
Containers::String stageName;
if (!TryParseStringValue(variantObject, "stage", stageName) ||
!TryParseShaderType(stageName, variant.stage)) {
return LoadResult("Shader manifest variant has an invalid stage: " + path);
}
Containers::String backendName;
if (!TryParseStringValue(variantObject, "backend", backendName) ||
!TryParseShaderBackend(backendName, variant.backend)) {
return LoadResult("Shader manifest variant has an invalid backend: " + path);
}
Containers::String languageName;
if (!TryParseStringValue(variantObject, "language", languageName) ||
!TryParseShaderLanguage(languageName, variant.language)) {
return LoadResult("Shader manifest variant has an invalid language: " + path);
}
Containers::String sourceCode;
if (TryParseStringValue(variantObject, "sourceCode", sourceCode)) {
variant.sourceCode = sourceCode;
} else {
Containers::String sourcePath;
if (!TryParseStringValue(variantObject, "source", sourcePath) &&
!TryParseStringValue(variantObject, "sourcePath", sourcePath)) {
return LoadResult("Shader manifest variant is missing source/sourceCode: " + path);
}
const Containers::String resolvedSourcePath = ResolveShaderDependencyPath(sourcePath, path);
if (!ReadShaderTextFile(resolvedSourcePath, variant.sourceCode)) {
return LoadResult("Failed to read shader variant source: " + resolvedSourcePath);
}
}
if (!TryParseStringValue(variantObject, "entryPoint", variant.entryPoint)) {
variant.entryPoint = GetDefaultEntryPoint(variant.language, variant.stage);
}
if (!TryParseStringValue(variantObject, "profile", variant.profile)) {
variant.profile = GetDefaultProfile(variant.language, variant.backend, variant.stage);
}
std::string keywordsArray;
if (TryExtractArray(variantObject, "keywords", keywordsArray) &&
!TryParseShaderKeywordsArray(keywordsArray, variant.requiredKeywords)) {
return LoadResult("Shader manifest variant keywords could not be parsed: " + path);
}
shader->AddPassVariant(passName, variant);
}
}
shader->m_memorySize = CalculateShaderMemorySize(*shader);
return LoadResult(shader.release());
}
} // namespace Resources
} // namespace XCEngine

View File

@@ -1,24 +0,0 @@
#pragma once
#include <XCEngine/Core/Containers/Array.h>
#include <XCEngine/Core/Containers/String.h>
#include <XCEngine/Core/IO/IResourceLoader.h>
#include <string>
namespace XCEngine {
namespace Resources {
bool LooksLikeShaderManifest(const std::string& sourceText);
bool CollectShaderManifestDependencyPaths(
const Containers::String& path,
const std::string& jsonText,
Containers::Array<Containers::String>& outDependencies);
LoadResult LoadShaderManifest(
const Containers::String& path,
const std::string& jsonText);
} // namespace Resources
} // namespace XCEngine

View File

@@ -7,6 +7,7 @@
#include <XCEngine/UI/Layout/LayoutEngine.h>
#include <XCEngine/UI/Layout/UITabStripLayout.h>
#include <XCEngine/UI/Style/DocumentStyleCompiler.h>
#include <XCEngine/UI/Widgets/UIScrollModel.h>
#include <XCEngine/UI/Widgets/UITabStripModel.h>
#include <algorithm>
@@ -985,14 +986,6 @@ std::string BuildNodeStateKeySegment(
return ToStdString(source.tagName) + "#" + std::to_string(siblingIndex);
}
float ComputeScrollOverflow(float contentExtent, float viewportExtent) {
return (std::max)(0.0f, contentExtent - viewportExtent);
}
float ClampScrollOffset(float offset, float contentExtent, float viewportExtent) {
return (std::max)(0.0f, (std::min)(offset, ComputeScrollOverflow(contentExtent, viewportExtent)));
}
bool RectContainsPoint(const UIRect& rect, const UIPoint& point) {
return point.x >= rect.x &&
point.x <= rect.x + rect.width &&
@@ -1885,7 +1878,7 @@ void ArrangeNode(
const auto found = verticalScrollOffsets.find(node.stateKey);
const float requestedOffset = found != verticalScrollOffsets.end() ? found->second : 0.0f;
node.scrollViewportRect = contentRect;
node.scrollOffsetY = ClampScrollOffset(
node.scrollOffsetY = Widgets::ClampUIScrollOffset(
requestedOffset,
node.contentDesiredSize.height,
contentRect.height);
@@ -1932,7 +1925,7 @@ RuntimeLayoutNode* FindDeepestScrollTarget(
return nullptr;
}
if (ComputeScrollOverflow(node.contentDesiredSize.height, node.scrollViewportRect.height) <= 0.0f) {
if (Widgets::ComputeUIScrollOverflow(node.contentDesiredSize.height, node.scrollViewportRect.height) <= 0.0f) {
return nullptr;
}
@@ -2001,7 +1994,7 @@ bool ApplyScrollWheelEvent(
scrollDebugSnapshot.lastTargetStateKey = hoveredScrollView->stateKey;
scrollDebugSnapshot.lastViewportRect = hoveredScrollView->scrollViewportRect;
scrollDebugSnapshot.lastOverflow = ComputeScrollOverflow(
scrollDebugSnapshot.lastOverflow = Widgets::ComputeUIScrollOverflow(
hoveredScrollView->contentDesiredSize.height,
hoveredScrollView->scrollViewportRect.height);
@@ -2022,18 +2015,18 @@ bool ApplyScrollWheelEvent(
: target->scrollOffsetY;
scrollDebugSnapshot.lastOffsetBefore = oldOffset;
const float scrollUnits = event.wheelDelta / 120.0f;
const float nextOffset = ClampScrollOffset(
oldOffset - scrollUnits * 48.0f,
const Widgets::UIScrollWheelResult scrollResult = Widgets::ApplyUIScrollWheel(
oldOffset,
event.wheelDelta,
target->contentDesiredSize.height,
target->scrollViewportRect.height);
scrollDebugSnapshot.lastOffsetAfter = nextOffset;
if (std::fabs(nextOffset - oldOffset) <= 0.01f) {
scrollDebugSnapshot.lastOffsetAfter = scrollResult.offsetAfter;
if (!scrollResult.changed) {
scrollDebugSnapshot.lastResult = "Scroll delta clamped to current offset";
return false;
}
verticalScrollOffsets[target->stateKey] = nextOffset;
verticalScrollOffsets[target->stateKey] = scrollResult.offsetAfter;
++scrollDebugSnapshot.handledWheelEventCount;
scrollDebugSnapshot.lastResult = "Handled";
return true;
@@ -2545,7 +2538,8 @@ void SyncScrollOffsets(
const RuntimeLayoutNode& node,
std::unordered_map<std::string, float>& verticalScrollOffsets) {
if (node.isScrollView) {
const float overflow = ComputeScrollOverflow(node.contentDesiredSize.height, node.scrollViewportRect.height);
const float overflow =
Widgets::ComputeUIScrollOverflow(node.contentDesiredSize.height, node.scrollViewportRect.height);
if (overflow <= 0.0f || node.scrollOffsetY <= 0.0f) {
verticalScrollOffsets.erase(node.stateKey);
} else {
@@ -3009,7 +3003,7 @@ UIScreenFrameResult UIDocumentScreenHost::BuildFrame(
if (const RuntimeLayoutNode* primaryScrollView = FindFirstScrollView(root); primaryScrollView != nullptr) {
m_scrollDebugSnapshot.primaryTargetStateKey = primaryScrollView->stateKey;
m_scrollDebugSnapshot.primaryViewportRect = primaryScrollView->scrollViewportRect;
m_scrollDebugSnapshot.primaryOverflow = ComputeScrollOverflow(
m_scrollDebugSnapshot.primaryOverflow = Widgets::ComputeUIScrollOverflow(
primaryScrollView->contentDesiredSize.height,
primaryScrollView->scrollViewportRect.height);
} else {

View File

@@ -0,0 +1,67 @@
#include <XCEngine/UI/Widgets/UIScrollModel.h>
#include <algorithm>
#include <cmath>
namespace XCEngine {
namespace UI {
namespace Widgets {
float ComputeUIScrollOverflow(float contentExtent, float viewportExtent) {
return (std::max)(0.0f, contentExtent - viewportExtent);
}
float ClampUIScrollOffset(float offset, float contentExtent, float viewportExtent) {
return (std::max)(0.0f, (std::min)(offset, ComputeUIScrollOverflow(contentExtent, viewportExtent)));
}
UIScrollWheelResult ApplyUIScrollWheel(
float offset,
float wheelDelta,
float contentExtent,
float viewportExtent,
float wheelStep,
float epsilon) {
UIScrollWheelResult result = {};
result.offsetBefore = ClampUIScrollOffset(offset, contentExtent, viewportExtent);
result.overflow = ComputeUIScrollOverflow(contentExtent, viewportExtent);
result.offsetAfter = result.offsetBefore;
if (result.overflow <= 0.0f || std::fabs(wheelDelta) <= epsilon || wheelStep <= 0.0f) {
return result;
}
const float scrollUnits = wheelDelta / 120.0f;
result.offsetAfter = ClampUIScrollOffset(
result.offsetBefore - scrollUnits * wheelStep,
contentExtent,
viewportExtent);
result.changed = std::fabs(result.offsetAfter - result.offsetBefore) > epsilon;
return result;
}
float EnsureUIScrollOffsetVisible(
float offset,
float itemStart,
float itemExtent,
float contentExtent,
float viewportExtent) {
const float clampedOffset = ClampUIScrollOffset(offset, contentExtent, viewportExtent);
if (viewportExtent <= 0.0f || itemExtent <= 0.0f) {
return clampedOffset;
}
const float itemEnd = itemStart + itemExtent;
const float viewportEnd = clampedOffset + viewportExtent;
float nextOffset = clampedOffset;
if (itemStart < clampedOffset) {
nextOffset = itemStart;
} else if (itemEnd > viewportEnd) {
nextOffset = itemEnd - viewportExtent;
}
return ClampUIScrollOffset(nextOffset, contentExtent, viewportExtent);
}
} // namespace Widgets
} // namespace UI
} // namespace XCEngine