Formalize editor skybox wiring and debug presets

This commit is contained in:
2026-04-06 03:28:20 +08:00
parent c7dc8d7484
commit 3547597bd2
12 changed files with 324 additions and 103 deletions

35
CMakePresets.json Normal file
View File

@@ -0,0 +1,35 @@
{
"version": 3,
"cmakeMinimumRequired": {
"major": 3,
"minor": 21,
"patch": 0
},
"configurePresets": [
{
"name": "vs2022-x64",
"displayName": "Visual Studio 2022 x64",
"generator": "Visual Studio 17 2022",
"binaryDir": "${sourceDir}/build"
}
],
"buildPresets": [
{
"name": "debug",
"displayName": "Build Debug",
"configurePreset": "vs2022-x64",
"configuration": "Debug"
}
],
"testPresets": [
{
"name": "debug",
"displayName": "Test Debug",
"configurePreset": "vs2022-x64",
"configuration": "Debug",
"output": {
"outputOnFailure": true
}
}
]
}

View File

@@ -1,10 +1,14 @@
#pragma once
#include "AssetReferenceEditorUtils.h"
#include "IComponentEditor.h"
#include "Core/IUndoManager.h"
#include "UI/UI.h"
#include <XCEngine/Components/CameraComponent.h>
#include <XCEngine/Core/Math/Color.h>
#include <string>
namespace XCEngine {
namespace Editor {
@@ -95,6 +99,87 @@ public:
camera->SetPrimary(primary);
});
bool skyboxEnabled = camera->IsSkyboxEnabled();
changed |= UI::ApplyPropertyChange(
UI::DrawPropertyBool("Skybox", skyboxEnabled),
undoManager,
kUndoLabel,
[&]() {
camera->SetSkyboxEnabled(skyboxEnabled);
});
if (skyboxEnabled) {
const ComponentEditorAssetUI::AssetReferenceInteraction skyboxMaterialInteraction =
ComponentEditorAssetUI::DrawAssetReferenceProperty(
"Skybox Material",
camera->GetSkyboxMaterialPath(),
"Drop Skybox Material",
{ ".mat" });
changed |= UI::ApplyPropertyChange(
skyboxMaterialInteraction.clearRequested && !camera->GetSkyboxMaterialPath().empty(),
undoManager,
kUndoLabel,
[camera]() {
camera->SetSkyboxMaterialPath(std::string());
});
changed |= UI::ApplyPropertyChange(
!skyboxMaterialInteraction.assignedPath.empty() &&
skyboxMaterialInteraction.assignedPath != camera->GetSkyboxMaterialPath(),
undoManager,
kUndoLabel,
[camera, assignedPath = skyboxMaterialInteraction.assignedPath]() {
camera->SetSkyboxMaterialPath(assignedPath);
});
if (camera->GetSkyboxMaterialPath().empty()) {
float topColor[4] = {
camera->GetSkyboxTopColor().r,
camera->GetSkyboxTopColor().g,
camera->GetSkyboxTopColor().b,
camera->GetSkyboxTopColor().a
};
changed |= UI::ApplyPropertyChange(
UI::DrawPropertyColor4("Skybox Top", topColor),
undoManager,
kUndoLabel,
[&]() {
camera->SetSkyboxTopColor(::XCEngine::Math::Color(
topColor[0], topColor[1], topColor[2], topColor[3]));
});
float horizonColor[4] = {
camera->GetSkyboxHorizonColor().r,
camera->GetSkyboxHorizonColor().g,
camera->GetSkyboxHorizonColor().b,
camera->GetSkyboxHorizonColor().a
};
changed |= UI::ApplyPropertyChange(
UI::DrawPropertyColor4("Skybox Horizon", horizonColor),
undoManager,
kUndoLabel,
[&]() {
camera->SetSkyboxHorizonColor(::XCEngine::Math::Color(
horizonColor[0], horizonColor[1], horizonColor[2], horizonColor[3]));
});
float bottomColor[4] = {
camera->GetSkyboxBottomColor().r,
camera->GetSkyboxBottomColor().g,
camera->GetSkyboxBottomColor().b,
camera->GetSkyboxBottomColor().a
};
changed |= UI::ApplyPropertyChange(
UI::DrawPropertyColor4("Skybox Bottom", bottomColor),
undoManager,
kUndoLabel,
[&]() {
camera->SetSkyboxBottomColor(::XCEngine::Math::Color(
bottomColor[0], bottomColor[1], bottomColor[2], bottomColor[3]));
});
}
}
float clearColor[4] = {
camera->GetClearColor().r,
camera->GetClearColor().g,

View File

@@ -471,7 +471,9 @@ void SceneManager::CreateDemoScene() {
ClearSceneLoadProgress();
::XCEngine::Components::GameObject* camera = CreateEntity("Main Camera", nullptr);
camera->AddComponent<::XCEngine::Components::CameraComponent>();
if (auto* cameraComponent = camera->AddComponent<::XCEngine::Components::CameraComponent>()) {
cameraComponent->SetSkyboxEnabled(true);
}
::XCEngine::Components::GameObject* light = CreateEntity("Directional Light", nullptr);
light->AddComponent<::XCEngine::Components::LightComponent>();

View File

@@ -98,6 +98,8 @@ struct BuiltInIconState {
BuiltInTexture folder;
BuiltInTexture gameObject;
BuiltInTexture scene;
BuiltInTexture cameraGizmo;
BuiltInTexture mainLightGizmo;
struct CachedAssetPreview {
BuiltInTexture texture;
std::unique_ptr<LoadedTexturePixels> decodedPixels;
@@ -150,6 +152,16 @@ std::filesystem::path ResolveSceneIconPath() {
return (exeDir / ".." / ".." / "resources" / "Icons" / "scene_icon.png").lexically_normal();
}
std::filesystem::path ResolveCameraGizmoIconPath() {
const std::filesystem::path exeDir(Platform::GetExecutableDirectoryUtf8());
return (exeDir / ".." / ".." / "resources" / "Icons" / "camera_gizmo.png").lexically_normal();
}
std::filesystem::path ResolveMainLightGizmoIconPath() {
const std::filesystem::path exeDir(Platform::GetExecutableDirectoryUtf8());
return (exeDir / ".." / ".." / "resources" / "Icons" / "main_light_gizmo.png").lexically_normal();
}
std::string NormalizePathKey(const std::filesystem::path& path) {
std::wstring key = path.lexically_normal().generic_wstring();
std::transform(key.begin(), key.end(), key.begin(), ::towlower);
@@ -959,6 +971,17 @@ void PruneAssetPreviewCache() {
}
}
BuiltInTexture* ResolveEditorTexture(EditorTextureIconKind kind) {
switch (kind) {
case EditorTextureIconKind::CameraGizmo:
return &g_icons.cameraGizmo;
case EditorTextureIconKind::MainLightGizmo:
return &g_icons.mainLightGizmo;
default:
return nullptr;
}
}
} // namespace
void InitializeBuiltInIcons(
@@ -985,6 +1008,28 @@ void InitializeBuiltInIcons(
if (LoadTextureFromFile(backend, device, commandQueue, ResolveSceneIconPath(), g_icons.scene, pendingUpload)) {
g_icons.pendingIconUploads.push_back(std::move(pendingUpload));
}
pendingUpload.reset();
if (LoadTextureFromFile(
backend,
device,
commandQueue,
ResolveCameraGizmoIconPath(),
g_icons.cameraGizmo,
pendingUpload)) {
g_icons.pendingIconUploads.push_back(std::move(pendingUpload));
}
pendingUpload.reset();
if (LoadTextureFromFile(
backend,
device,
commandQueue,
ResolveMainLightGizmoIconPath(),
g_icons.mainLightGizmo,
pendingUpload)) {
g_icons.pendingIconUploads.push_back(std::move(pendingUpload));
}
}
void ShutdownBuiltInIcons() {
@@ -997,6 +1042,8 @@ void ShutdownBuiltInIcons() {
ResetTexture(g_icons.folder);
ResetTexture(g_icons.gameObject);
ResetTexture(g_icons.scene);
ResetTexture(g_icons.cameraGizmo);
ResetTexture(g_icons.mainLightGizmo);
g_icons.backend = nullptr;
g_icons.device = nullptr;
g_icons.commandQueue = nullptr;
@@ -1041,6 +1088,22 @@ void DrawAssetIcon(ImDrawList* drawList, const ImVec2& min, const ImVec2& max, A
DrawBuiltInFileIcon(drawList, min, max);
}
bool DrawEditorTextureIcon(
ImDrawList* drawList,
const ImVec2& min,
const ImVec2& max,
EditorTextureIconKind kind) {
MaintainIconRuntimeState();
const BuiltInTexture* texture = ResolveEditorTexture(kind);
if (texture == nullptr || !texture->IsValid()) {
return false;
}
DrawTextureIcon(drawList, *texture, min, max);
return true;
}
bool DrawTextureAssetPreview(
ImDrawList* drawList,
const ImVec2& min,

View File

@@ -19,6 +19,11 @@ enum class AssetIconKind {
Scene
};
enum class EditorTextureIconKind {
CameraGizmo,
MainLightGizmo
};
void InitializeBuiltInIcons(
ImGuiBackendBridge& backend,
ID3D12Device* device,
@@ -27,6 +32,11 @@ void InitializeBuiltInIcons(
void ShutdownBuiltInIcons();
void DrawAssetIcon(ImDrawList* drawList, const ImVec2& min, const ImVec2& max, AssetIconKind kind);
bool DrawEditorTextureIcon(
ImDrawList* drawList,
const ImVec2& min,
const ImVec2& max,
EditorTextureIconKind kind);
bool DrawTextureAssetPreview(
ImDrawList* drawList,
const ImVec2& min,

View File

@@ -378,7 +378,7 @@ bool SceneViewportEditorOverlayPassRenderer::Render(
const Rendering::RenderContext& renderContext,
const Rendering::RenderSurface& surface,
const SceneViewportOverlayFrameData& frameData) {
if (!frameData.HasOverlayPrimitives() || !renderContext.IsValid() || renderContext.commandList == nullptr) {
if (!frameData.HasRenderPassPrimitives() || !renderContext.IsValid() || renderContext.commandList == nullptr) {
return true;
}
@@ -411,33 +411,10 @@ bool SceneViewportEditorOverlayPassRenderer::Render(
AppendScreenTriangleVertices(targetVertices, triangle);
}
std::vector<const SceneViewportOverlaySpritePrimitive*> depthTestedSprites = {};
std::vector<const SceneViewportOverlaySpritePrimitive*> alwaysOnTopSprites = {};
depthTestedSprites.reserve(frameData.worldSprites.size());
alwaysOnTopSprites.reserve(frameData.worldSprites.size());
for (const SceneViewportOverlaySpritePrimitive& sprite : frameData.worldSprites) {
if (sprite.sizePixels.x <= Math::EPSILON || sprite.sizePixels.y <= Math::EPSILON) {
continue;
}
if (sprite.depthMode == SceneViewportOverlayDepthMode::DepthTested) {
depthTestedSprites.push_back(&sprite);
} else {
alwaysOnTopSprites.push_back(&sprite);
}
}
const auto sortBackToFront =
[](const SceneViewportOverlaySpritePrimitive* lhs, const SceneViewportOverlaySpritePrimitive* rhs) {
return lhs->sortDepth > rhs->sortDepth;
};
std::sort(depthTestedSprites.begin(), depthTestedSprites.end(), sortBackToFront);
std::sort(alwaysOnTopSprites.begin(), alwaysOnTopSprites.end(), sortBackToFront);
const size_t lineVertexCount = depthTestedLineVertices.size() + alwaysOnTopLineVertices.size();
const size_t screenTriangleVertexCount =
depthTestedScreenTriangleVertices.size() + alwaysOnTopScreenTriangleVertices.size();
const size_t spriteVertexCount = (depthTestedSprites.size() + alwaysOnTopSprites.size()) * 6u;
const size_t spriteVertexCount = 0u;
if (lineVertexCount == 0u && screenTriangleVertexCount == 0u && spriteVertexCount == 0u) {
return true;
}
@@ -471,35 +448,6 @@ bool SceneViewportEditorOverlayPassRenderer::Render(
screenTriangleVertices.size() * sizeof(OverlayScreenTriangleVertex));
}
std::vector<OverlaySpriteVertex> spriteVertices = {};
std::array<OverlaySpriteBatchRange, kSceneViewportOverlaySpriteResourceCount> depthTestedSpriteBatches = {};
std::array<OverlaySpriteBatchRange, kSceneViewportOverlaySpriteResourceCount> alwaysOnTopSpriteBatches = {};
if (spriteVertexCount > 0u) {
spriteVertices.reserve(spriteVertexCount);
const auto appendSpriteBatches =
[&spriteVertices](
const std::vector<const SceneViewportOverlaySpritePrimitive*>& sprites,
std::array<OverlaySpriteBatchRange, kSceneViewportOverlaySpriteResourceCount>& outBatches) {
for (size_t textureIndex = 0; textureIndex < kSceneViewportOverlaySpriteResourceCount; ++textureIndex) {
OverlaySpriteBatchRange range = {};
range.firstVertex = static_cast<uint32_t>(spriteVertices.size());
for (const SceneViewportOverlaySpritePrimitive* sprite : sprites) {
if (sprite == nullptr ||
GetSceneViewportOverlaySpriteResourceIndex(sprite->textureKind) != textureIndex) {
continue;
}
AppendSpriteVertices(spriteVertices, *sprite);
}
range.vertexCount = static_cast<uint32_t>(spriteVertices.size()) - range.firstVertex;
outBatches[textureIndex] = range;
}
};
appendSpriteBatches(depthTestedSprites, depthTestedSpriteBatches);
appendSpriteBatches(alwaysOnTopSprites, alwaysOnTopSpriteBatches);
m_spriteVertexBuffer->SetData(spriteVertices.data(), spriteVertices.size() * sizeof(OverlaySpriteVertex));
}
OverlayConstants constants = {};
constants.viewProjection = BuildSceneViewportViewProjectionMatrix(
frameData.overlay,
@@ -564,49 +512,6 @@ bool SceneViewportEditorOverlayPassRenderer::Render(
}
}
if (spriteVertexCount > 0u) {
commandList->SetPrimitiveTopology(RHI::PrimitiveTopology::TriangleList);
RHI::RHIResourceView* vertexBuffers[] = { m_spriteVertexBufferView };
const uint64_t offsets[] = { 0u };
const uint32_t strides[] = { static_cast<uint32_t>(sizeof(OverlaySpriteVertex)) };
commandList->SetVertexBuffers(0, 1, vertexBuffers, offsets, strides);
const auto drawSpriteBatchGroup =
[this, commandList](
RHI::RHIPipelineState* pipelineState,
const std::array<OverlaySpriteBatchRange, kSceneViewportOverlaySpriteResourceCount>& batches) {
if (pipelineState == nullptr) {
return;
}
commandList->SetPipelineState(pipelineState);
for (size_t textureIndex = 0; textureIndex < kSceneViewportOverlaySpriteResourceCount; ++textureIndex) {
const OverlaySpriteBatchRange& batch = batches[textureIndex];
if (!batch.HasVertices()) {
continue;
}
RHI::RHIDescriptorSet* const textureSet =
m_overlaySpriteResources.GetTextureSet(
GetSceneViewportOverlaySpriteTextureKindByIndex(textureIndex));
if (textureSet == nullptr) {
continue;
}
RHI::RHIDescriptorSet* descriptorSets[] = {
m_constantSet,
textureSet,
m_samplerSet
};
commandList->SetGraphicsDescriptorSets(0, 3, descriptorSets, m_spritePipelineLayout);
commandList->Draw(batch.vertexCount, 1, batch.firstVertex, 0);
}
};
drawSpriteBatchGroup(m_depthTestedSpritePipelineState, depthTestedSpriteBatches);
drawSpriteBatchGroup(m_alwaysOnTopSpritePipelineState, alwaysOnTopSpriteBatches);
}
if (screenTriangleVertexCount > 0u) {
commandList->SetPrimitiveTopology(RHI::PrimitiveTopology::TriangleList);
RHI::RHIResourceView* vertexBuffers[] = { m_screenTriangleVertexBufferView };

View File

@@ -110,6 +110,10 @@ struct SceneViewportOverlayFrameData {
return overlay.valid && (!worldLines.empty() || !worldSprites.empty() || !screenTriangles.empty());
}
bool HasRenderPassPrimitives() const {
return overlay.valid && (!worldLines.empty() || !screenTriangles.empty());
}
bool HasWorldOverlay() const {
return HasOverlayPrimitives();
}

View File

@@ -1,6 +1,12 @@
#include "SceneViewportHudOverlay.h"
#include "SceneViewportOrientationGizmo.h"
#include "SceneViewportMath.h"
#include "UI/BuiltInIcons.h"
#include <algorithm>
#include <vector>
namespace XCEngine {
namespace Editor {
@@ -11,6 +17,97 @@ bool IsViewportRectValid(const ImVec2& viewportMin, const ImVec2& viewportMax) {
viewportMax.y - viewportMin.y > 1.0f;
}
bool DrawSceneViewportSpriteIcon(
ImDrawList* drawList,
const ImVec2& min,
const ImVec2& max,
SceneViewportOverlaySpriteTextureKind textureKind) {
switch (textureKind) {
case SceneViewportOverlaySpriteTextureKind::Camera:
return UI::DrawEditorTextureIcon(
drawList,
min,
max,
UI::EditorTextureIconKind::CameraGizmo);
case SceneViewportOverlaySpriteTextureKind::Light:
return UI::DrawEditorTextureIcon(
drawList,
min,
max,
UI::EditorTextureIconKind::MainLightGizmo);
default:
return false;
}
}
void DrawSceneViewportSceneIcons(
ImDrawList* drawList,
const SceneViewportHudOverlayData& overlay,
const ImVec2& viewportMin,
const ImVec2& viewportMax) {
if (drawList == nullptr ||
!overlay.showSceneIcons ||
overlay.overlayFrameData == nullptr ||
overlay.overlayFrameData->worldSprites.empty()) {
return;
}
const float viewportWidth = viewportMax.x - viewportMin.x;
const float viewportHeight = viewportMax.y - viewportMin.y;
if (viewportWidth <= 1.0f || viewportHeight <= 1.0f) {
return;
}
std::vector<const SceneViewportOverlaySpritePrimitive*> sortedSprites = {};
sortedSprites.reserve(overlay.overlayFrameData->worldSprites.size());
for (const SceneViewportOverlaySpritePrimitive& sprite : overlay.overlayFrameData->worldSprites) {
sortedSprites.push_back(&sprite);
}
std::sort(
sortedSprites.begin(),
sortedSprites.end(),
[](const SceneViewportOverlaySpritePrimitive* lhs, const SceneViewportOverlaySpritePrimitive* rhs) {
if (lhs == rhs) {
return false;
}
if (lhs == nullptr) {
return false;
}
if (rhs == nullptr) {
return true;
}
return lhs->sortDepth > rhs->sortDepth;
});
for (const SceneViewportOverlaySpritePrimitive* sprite : sortedSprites) {
if (sprite == nullptr ||
sprite->sizePixels.x <= Math::EPSILON ||
sprite->sizePixels.y <= Math::EPSILON) {
continue;
}
const SceneViewportProjectedPoint projectedPoint = ProjectSceneViewportWorldPoint(
overlay.sceneOverlay,
viewportWidth,
viewportHeight,
sprite->worldPosition);
if (!projectedPoint.visible) {
continue;
}
const ImVec2 halfSize(sprite->sizePixels.x * 0.5f, sprite->sizePixels.y * 0.5f);
const ImVec2 center(
viewportMin.x + projectedPoint.screenPosition.x,
viewportMin.y + projectedPoint.screenPosition.y);
DrawSceneViewportSpriteIcon(
drawList,
ImVec2(center.x - halfSize.x, center.y - halfSize.y),
ImVec2(center.x + halfSize.x, center.y + halfSize.y),
sprite->textureKind);
}
}
} // namespace
void DrawSceneViewportHudOverlay(
@@ -25,6 +122,11 @@ void DrawSceneViewportHudOverlay(
}
drawList->PushClipRect(viewportMin, viewportMax, true);
DrawSceneViewportSceneIcons(
drawList,
overlay,
viewportMin,
viewportMax);
if (overlay.showOrientationGizmo) {
DrawSceneViewportOrientationGizmo(
drawList,

View File

@@ -1,6 +1,6 @@
#pragma once
#include "IViewportHostService.h"
#include "SceneViewportEditorOverlayData.h"
#include <imgui.h>
@@ -26,18 +26,28 @@ struct SceneViewportHudOverlayHitResult {
struct SceneViewportHudOverlayData {
SceneViewportOverlayData sceneOverlay = {};
bool showOrientationGizmo = true;
bool showSceneIcons = true;
const SceneViewportOverlayFrameData* overlayFrameData = nullptr;
bool HasVisibleElements() const {
return sceneOverlay.valid && showOrientationGizmo;
return sceneOverlay.valid &&
(showOrientationGizmo ||
(showSceneIcons &&
overlayFrameData != nullptr &&
!overlayFrameData->worldSprites.empty()));
}
};
inline SceneViewportHudOverlayData BuildSceneViewportHudOverlayData(
const SceneViewportOverlayData& sceneOverlay,
bool showOrientationGizmo = true) {
bool showOrientationGizmo = true,
const SceneViewportOverlayFrameData* overlayFrameData = nullptr,
bool showSceneIcons = true) {
SceneViewportHudOverlayData data = {};
data.sceneOverlay = sceneOverlay;
data.showOrientationGizmo = showOrientationGizmo;
data.showSceneIcons = showSceneIcons;
data.overlayFrameData = overlayFrameData;
return data;
}

View File

@@ -143,6 +143,7 @@ inline bool ShouldFocusSceneViewportAfterInteraction(
struct SceneViewportPresentationRequest {
IEditorContext* context = nullptr;
IViewportHostService* viewportHostService = nullptr;
const SceneViewportOverlayFrameData* overlayFrameData = nullptr;
bool hasInteractiveViewport = false;
SceneViewportFrameGeometry geometry = {};
SceneViewportTransformGizmoFrameOptions gizmoFrameOptions = {};
@@ -182,7 +183,10 @@ inline void RefreshAndDrawSceneViewportPresentation(const SceneViewportPresentat
DrawSceneViewportHudOverlay(
request.drawList,
BuildSceneViewportHudOverlayData(overlay),
BuildSceneViewportHudOverlayData(
overlay,
true,
request.overlayFrameData),
request.viewportMin,
request.viewportMax);
}

View File

@@ -84,7 +84,7 @@ inline SceneViewportRenderPlanBuildResult BuildSceneViewportRenderPlan(
}
}
if (editorOverlayFrameData.HasOverlayPrimitives() &&
if (editorOverlayFrameData.HasRenderPassPrimitives() &&
overlayPassFactory != nullptr) {
std::unique_ptr<Rendering::RenderPass> overlayPass = overlayPassFactory(editorOverlayFrameData);
if (overlayPass != nullptr) {

View File

@@ -194,6 +194,7 @@ void SceneViewPanel::Render() {
SceneViewportPresentationRequest presentationRequest = {};
presentationRequest.context = m_context;
presentationRequest.viewportHostService = viewportHostService;
presentationRequest.overlayFrameData = interactionFrameState.overlayFrameData;
presentationRequest.hasInteractiveViewport = hasInteractiveViewport;
presentationRequest.geometry = frameGeometry;
presentationRequest.gizmoFrameOptions = toolState.gizmoFrameOptions;