Skip to content
Merged
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
7 changes: 7 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,7 @@ set(CORE_SOURCES
src/Core/Application.cpp
src/Core/CommandLine.cpp
src/Core/File.cpp
src/Core/FileWatcher.cpp
src/Core/Logger.cpp
src/Core/ScopedTimer.cpp
src/Core/Serializable.cpp
Expand All @@ -128,6 +129,7 @@ set(DEBUG_SOURCES
src/Debug/Benchmark.cpp
src/Debug/DebugLineRenderer.cpp
src/Debug/DebugOverlay.cpp
src/Debug/ShaderHotReload.cpp
src/Debug/SystemMetrics.cpp
)

Expand Down Expand Up @@ -373,6 +375,7 @@ endforeach()
if(GLSLC)
set(ENGINE_SPIRV_DIR "${CMAKE_CURRENT_BINARY_DIR}/shaders")
file(MAKE_DIRECTORY "${ENGINE_SPIRV_DIR}")
target_compile_definitions(Engine PRIVATE SLEAK_GLSLC_PATH="${GLSLC}")
foreach(_src ${ENGINE_VULKAN_SHADERS})
get_filename_component(_name "${_src}" NAME)
set(_spv "${ENGINE_SPIRV_DIR}/${_name}.spv")
Expand Down Expand Up @@ -429,6 +432,10 @@ add_dependencies(Engine CompileShaders)
set_property(GLOBAL PROPERTY ENGINE_SPIRV_FILES ${ENGINE_SPIRV_FILES})
set_property(GLOBAL PROPERTY ENGINE_COMMITTED_SPIRV ${ENGINE_COMMITTED_SPIRV})

target_compile_definitions(Engine
PUBLIC $<$<CONFIG:Debug>:SLEAK_HOT_RELOAD>
PRIVATE SLEAK_ENGINE_SHADER_DIR="${CMAKE_CURRENT_SOURCE_DIR}/assets/shaders")

function(_engine_collect_targets dir out)
get_property(_targets DIRECTORY "${dir}" PROPERTY BUILDSYSTEM_TARGETS)
get_property(_subdirs DIRECTORY "${dir}" PROPERTY SUBDIRECTORIES)
Expand Down
12 changes: 10 additions & 2 deletions include/private/Graphics/Common/Renderer.hpp
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
#pragma once


#include "RenderContext.hpp"
#include <Core/OSDef.hpp>
#include <Core/Timer.hpp>
#include <string>
#include <vector>

#include "RenderContext.hpp"

namespace Sleak {

Expand Down Expand Up @@ -49,6 +51,12 @@ class ENGINE_API Renderer {
virtual void Cleanup() = 0;
/// Blocks until the GPU has finished all outstanding work.
virtual void WaitIdle() {}
/// Rebuilds what uses the given shader stems from the files on disk.
/// Returns false when the backend has no hot reload support.
virtual bool ReloadShaders(const std::vector<std::string>& stems) {
(void)stems;
return false;
}
/// Flushes any buffered upload/staging transfers still in flight.
virtual void FlushPendingTransfers() {}

Expand Down
10 changes: 10 additions & 0 deletions include/private/Graphics/DirectX11/DirectX11Renderer.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,10 @@ class ENGINE_API DirectX11Renderer : public Renderer, public RenderContext {
void BeginRender() override;
void EndRender() override;
void Cleanup() override;
#ifdef SLEAK_HOT_RELOAD
/// Recompiles every live shader and fixed pass built from one of the stems.
bool ReloadShaders(const std::vector<std::string>& stems) override;
#endif

/// Allocates the depth/stencil texture and view sized to the swapchain.
bool CreateDepthStencilBuffer(uint32_t width, uint32_t height);
Expand Down Expand Up @@ -200,6 +204,12 @@ class ENGINE_API DirectX11Renderer : public Renderer, public RenderContext {
bool m_tonemapResourcesCreated = false;
/// Allocates the HDR offscreen target and tonemap shaders/sampler.
bool CreateTonemapResources();
/// Compiles tonemap.hlsl, writing the outputs only on success.
bool CompileTonemapShaders(ID3D11VertexShader** vs, ID3D11PixelShader** ps);
#ifdef SLEAK_HOT_RELOAD
/// Swaps in recompiled tonemap shaders, keeping the old ones on failure.
void ReloadTonemapShaders();
#endif
void CleanupTonemapResources();
/// Runs the ACES tonemap full-screen pass from the HDR target to the back buffer.
void ExecuteTonemapPass();
Expand Down
45 changes: 45 additions & 0 deletions include/private/Graphics/DirectX11/DirectX11Shader.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@
#include <stdexcept>
#include <string>
#include <vector>
#ifdef SLEAK_HOT_RELOAD
#include <unordered_set>
#endif

#include "../Common/Shader.hpp"

Expand All @@ -20,6 +23,8 @@ class DirectX11Shader : public Shader {
public:
DirectX11Shader(ID3D11Device* device);
~DirectX11Shader() override;
DirectX11Shader(const DirectX11Shader&) = delete;
DirectX11Shader& operator=(const DirectX11Shader&) = delete;

/// Compiles the combined-path convention (vs_main/ps_main entry points) from a single HLSL file.
bool compile(const std::string& shaderPath) override;
Expand All @@ -33,7 +38,41 @@ class DirectX11Shader : public Shader {
ID3D11InputLayout* createInputLayout();
ID3D11InputLayout* getInputLayout() const { return Layout.Get(); }

#ifdef SLEAK_HOT_RELOAD
/// Rebuilds stages and input layout, keeping the old ones on failure.
bool Reload();
/// Shares the stages and layout of a shader built from the same files.
void AdoptStages(const DirectX11Shader& source);
/// Returns true when both shaders compile the same files and entry points.
bool HasSameSources(const DirectX11Shader& other) const {
return m_sources == other.m_sources;
}
/// Returns the stem of the vertex source, e.g. skybox for skybox.hlsl.
const std::string& GetStem() const { return m_vertStem; }
/// Returns true when the vertex or pixel source has the given stem.
bool UsesStem(const std::string& stem) const {
return stem == m_vertStem || stem == m_fragStem;
}
/// Returns every live DirectX11Shader instance.
static const std::unordered_set<DirectX11Shader*>& GetLiveShaders();
#endif

private:
/// Files and entry points the two stages compile from.
struct StageSources {
std::string vertPath;
std::string vertEntry;
std::string fragPath;
std::string fragEntry;

bool operator==(const StageSources&) const = default;
};

/// Compiles both stages into new objects and swaps them in on success.
bool BuildStages(StageSources sources);
/// Creates an input layout matching the inputs of the vertex bytecode.
bool CreateLayoutFor(ID3DBlob* vertexBlob,
Microsoft::WRL::ComPtr<ID3D11InputLayout>& layout);
/// Compiles one HLSL entry point/profile via D3DCompile into a shader object and bytecode blob.
template <typename T>
bool compileShader(const std::string& filePath,
Expand All @@ -54,6 +93,12 @@ class DirectX11Shader : public Shader {
m_vertexShaderBlob; // For input layout creation
Microsoft::WRL::ComPtr<ID3DBlob> m_pixelShaderBlob;

#ifdef SLEAK_HOT_RELOAD
StageSources m_sources;
std::string m_vertStem;
std::string m_fragStem;
#endif

D3D11_INPUT_ELEMENT_DESC DefaultLayout[5] = {
// Semantic Index Format Slot Offset Class Step Rate
{"POSITION", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 0, D3D11_INPUT_PER_VERTEX_DATA, 0},
Expand Down
21 changes: 21 additions & 0 deletions include/private/Graphics/DirectX12/DirectX12Renderer.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,10 @@ class ENGINE_API DirectX12Renderer : public Renderer, public RenderContext {
void EndRender() override;
void Cleanup() override;
void WaitIdle() override { WaitForGPU(); }
#ifdef SLEAK_HOT_RELOAD
/// Rebuilds the PSOs of live shaders and fixed passes using the stems.
bool ReloadShaders(const std::vector<std::string>& stems) override;
#endif

virtual void Resize(uint32_t width, uint32_t height) override;

Expand Down Expand Up @@ -136,6 +140,23 @@ class ENGINE_API DirectX12Renderer : public Renderer, public RenderContext {
/// Returns the cached PSO for the key, building it on a miss.
ID3D12PipelineState* GetPipeline(const PipelineKey& key, ID3DBlob* vs,
ID3DBlob* ps);
/// Returns the default-state PSO for a vertex/pixel shader pair.
ID3D12PipelineState* GetShaderPipeline(ID3DBlob* vs, ID3DBlob* ps);
#ifdef SLEAK_HOT_RELOAD
/// Recompiles live shaders using the stems, collecting their old PSOs.
void ReloadLiveShaders(const std::vector<std::string>& stems,
std::vector<ID3D12PipelineState*>& replaced);
/// Rebuilds a fixed pass PSO, keeping the old one on failure.
void ReloadPassPipeline(ID3D12PipelineState*& pso, bool& failed,
bool (DirectX12Renderer::*create)(),
const char* stem,
std::vector<ID3D12PipelineState*>& replaced);
/// Recompiles the shadow vertex shader and rebuilds the shadow PSO.
void ReloadShadowPassPSO(std::vector<ID3D12PipelineState*>& replaced);
/// Drops cached PSOs that were replaced and that nothing binds anymore.
void PruneReplacedPipelines(
const std::vector<ID3D12PipelineState*>& replaced);
#endif
/// Reapplies the last shader PSO after a pass override ends.
void RestoreShaderPipeline();
/// Copies the light/fog constants into the ring and binds them at b2.
Expand Down
54 changes: 51 additions & 3 deletions include/private/Graphics/DirectX12/DirectX12Shader.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@
#include <stdexcept>
#include <string>
#include <vector>
#ifdef SLEAK_HOT_RELOAD
#include <unordered_set>
#endif

#include "../Common/Shader.hpp"

Expand All @@ -20,6 +23,8 @@ class DirectX12Shader : public Shader {
public:
DirectX12Shader(ID3D12Device* device);
~DirectX12Shader() override;
DirectX12Shader(const DirectX12Shader&) = delete;
DirectX12Shader& operator=(const DirectX12Shader&) = delete;

/// Compiles the combined-path convention (vs_main/ps_main entry points) from a single HLSL file.
bool compile(const std::string& shaderPath) override;
Expand All @@ -39,12 +44,50 @@ class DirectX12Shader : public Shader {
/// Routes bind() through the renderer so pass-specific PSOs are respected.
void SetRenderer(DirectX12Renderer* renderer);

#ifdef SLEAK_HOT_RELOAD
/// Recompiles both stages from their files without touching the live ones.
bool RecompileStages(Microsoft::WRL::ComPtr<ID3DBlob>& vs,
Microsoft::WRL::ComPtr<ID3DBlob>& ps) const;
/// Swaps in new bytecode together with the PSO built from it.
void ReplaceStages(Microsoft::WRL::ComPtr<ID3DBlob> vs,
Microsoft::WRL::ComPtr<ID3DBlob> ps,
ID3D12PipelineState* pso);
/// Returns true when both shaders compile the same files and entry points.
bool HasSameSources(const DirectX12Shader& other) const {
return m_sources == other.m_sources;
}
/// Returns the stem of the vertex source, e.g. skybox for skybox_dx12.hlsl.
const std::string& GetStem() const { return m_vertStem; }
/// Returns true when the vertex or pixel source has the given stem.
bool UsesStem(const std::string& stem) const {
return stem == m_vertStem || stem == m_fragStem;
}
/// Returns every live DirectX12Shader instance.
static const std::unordered_set<DirectX12Shader*>& GetLiveShaders();
#endif

private:
/// Files and entry points the two stages compile from.
struct StageSources {
std::string vertPath;
std::string vertEntry;
std::string fragPath;
std::string fragEntry;

bool operator==(const StageSources&) const = default;
};

/// Compiles both stages and swaps them in only when both succeed.
bool BuildStages(StageSources sources);
/// Compiles both stages of the given sources into new blobs.
bool CompileSources(const StageSources& sources,
Microsoft::WRL::ComPtr<ID3DBlob>& vs,
Microsoft::WRL::ComPtr<ID3DBlob>& ps) const;
/// Compiles one HLSL entry point/profile via D3DCompileFromFile into bytecode.
bool compileShader(const std::string& filePath,
const std::string& entryPoint,
const std::string& profile,
Microsoft::WRL::ComPtr<ID3DBlob>& blob);
const std::string& entryPoint,
const std::string& profile,
Microsoft::WRL::ComPtr<ID3DBlob>& blob) const;

Microsoft::WRL::ComPtr<ID3D12Device> m_device;

Expand All @@ -53,6 +96,11 @@ class DirectX12Shader : public Shader {
Microsoft::WRL::ComPtr<ID3D12PipelineState> m_pipelineState;
ID3D12GraphicsCommandList* m_commandList = nullptr;
DirectX12Renderer* m_renderer = nullptr;
#ifdef SLEAK_HOT_RELOAD
StageSources m_sources;
std::string m_vertStem;
std::string m_fragStem;
#endif
};

} // namespace RenderEngine
Expand Down
4 changes: 4 additions & 0 deletions include/private/Graphics/OpenGL/OpenGLRenderer.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,10 @@ class ENGINE_API OpenGLRenderer : public Renderer, public RenderContext {
void BeginRender() override;
void EndRender() override;
void Cleanup() override;
#ifdef SLEAK_HOT_RELOAD
/// Relinks every live shader built from one of the given stems.
bool ReloadShaders(const std::vector<std::string>& stems) override;
#endif

virtual uint32_t GetFeatureCaps() const override {
return CapDeferred | CapSSAO | CapIBL | CapShadows;
Expand Down
34 changes: 31 additions & 3 deletions include/private/Graphics/OpenGL/OpenGLShader.hpp
Original file line number Diff line number Diff line change
@@ -1,9 +1,15 @@
#ifndef OPENGLSHADER_HPP_
#define OPENGLSHADER_HPP_

#include "../Common/Shader.hpp"
#include <glad/glad.h>

#include <string>

#include "../Common/Shader.hpp"
#ifdef SLEAK_HOT_RELOAD
#include <unordered_set>
#endif

namespace Sleak {
namespace RenderEngine {

Expand All @@ -12,6 +18,8 @@ class ENGINE_API OpenGLShader : public Shader {
public:
OpenGLShader();
~OpenGLShader() override;
OpenGLShader(const OpenGLShader&) = delete;
OpenGLShader& operator=(const OpenGLShader&) = delete;

/// Splits a combined shader file into vertex/fragment stages and links them.
bool compile(const std::string& shaderPath) override;
Expand All @@ -21,13 +29,33 @@ class ENGINE_API OpenGLShader : public Shader {

GLuint GetProgram() const { return m_program; }

private:
/// Rebuilds the program from its files, keeping the old one on failure.
bool Reload();
/// Returns the stem of the vertex source, e.g. gbuffer for gbuffer_gl.vert.
const std::string& GetStem() const { return m_vertStem; }
/// Returns true when the vertex or fragment source has the given stem.
bool UsesStem(const std::string& stem) const {
return stem == m_vertStem || stem == m_fragStem;
}

#ifdef SLEAK_HOT_RELOAD
/// Returns every live OpenGLShader instance.
static const std::unordered_set<OpenGLShader*>& GetLiveShaders();
#endif

private:
/// Compiles a single shader stage from source text.
bool CompileShaderSource(const std::string& source, GLenum type,
GLuint& shader);
const std::string& path, GLuint& shader);
/// Links a new program from the stored paths and swaps it in on success.
bool BuildProgram();
std::string ReadFile(const std::string& path);

GLuint m_program = 0;
std::string m_vertPath;
std::string m_fragPath;
std::string m_vertStem;
std::string m_fragStem;
};

} // namespace RenderEngine
Expand Down
Loading
Loading