diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index a49e2baed..2fa0433e7 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -86,15 +86,34 @@ jobs: python ThirdParty/ConanRecipes/build_recipes.py --export-only - name: Configure CMake - run: cmake -B ${{github.workspace}}/build -G=Ninja -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} + run: cmake -B ${{github.workspace}}/build -G=Ninja -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} -DCI=ON - name: Build run: cmake --build ${{github.workspace}}/build --config ${{env.BUILD_TYPE}} -j ${{env.MAKE_THREAD_NUM}} + # Elevated Windows processes ignore VK_DRIVER_FILES, so register SwiftShader in the loader's trusted driver list. + - name: Register SwiftShader Vulkan Driver + if: runner.os == 'Windows' + shell: pwsh + run: | + $manifest = (Resolve-Path "${{github.workspace}}/build/Dist/Explosion/Binaries/vk_swiftshader_icd.json").Path + $driverRegistry = 'HKLM:\SOFTWARE\Khronos\Vulkan\Drivers' + New-Item -Path $driverRegistry -Force | Out-Null + New-ItemProperty -Path $driverRegistry -Name $manifest -PropertyType DWord -Value 0 -Force | Out-Null + - name: Test working-directory: ${{github.workspace}}/build run: ctest -C ${{env.BUILD_TYPE}} --extra-verbose + - name: Upload Rendering Test Artifacts + if: failure() + uses: actions/upload-artifact@v4 + with: + name: rendering-test-${{runner.os}}-${{runner.arch}} + path: ${{github.workspace}}/build/Test/Generated/RenderingSample + if-no-files-found: warn + retention-days: 14 + - name: Install run: cmake --build ${{github.workspace}}/build --config ${{env.BUILD_TYPE}} --target install -j ${{env.MAKE_THREAD_NUM}} diff --git a/CMake/Target.cmake b/CMake/Target.cmake index c8f01ba04..537ef61b5 100644 --- a/CMake/Target.cmake +++ b/CMake/Target.cmake @@ -3,6 +3,7 @@ include(CMakePackageConfigHelpers) option(BUILD_TEST "Build unit tests" ON) option(BUILD_BENCHMARK "Build benchmarks" ON) +option(CI "Build for continuous integration" OFF) set(GENERATED_DIR ${CMAKE_BINARY_DIR}/Generated) set(GENERATED_API_HEADER_DIR ${GENERATED_DIR}/Api) @@ -452,6 +453,27 @@ function(exp_get_runtime_output_dir) endif () endfunction() +function(exp_set_runtime_rpath) + set(options "") + set(singleValueArgs NAME) + set(multiValueArgs "") + cmake_parse_arguments(arg "${options}" "${singleValueArgs}" "${multiValueArgs}" ${ARGN}) + + if (APPLE) + set(runtime_origin "@executable_path") + elseif (UNIX) + set(runtime_origin "$ORIGIN") + else () + return() + endif () + + set_target_properties( + ${arg_NAME} PROPERTIES + BUILD_RPATH "${runtime_origin}" + INSTALL_RPATH "${runtime_origin}" + ) +endfunction() + function(exp_add_executable) set(options NOT_INSTALL) set(singleValueArgs NAME FOLDER) @@ -491,13 +513,7 @@ function(exp_add_executable) ${arg_NAME} PROPERTIES RUNTIME_OUTPUT_DIRECTORY ${runtime_output_dir} ) - if (APPLE) - set_target_properties( - ${arg_NAME} PROPERTIES - BUILD_RPATH "@executable_path" - INSTALL_RPATH "@executable_path" - ) - endif () + exp_set_runtime_rpath(NAME ${arg_NAME}) target_include_directories( ${arg_NAME} @@ -618,6 +634,8 @@ function(exp_add_library) ) if ("${arg_TYPE}" STREQUAL "SHARED") + exp_set_runtime_rpath(NAME ${arg_NAME}) + exp_get_runtime_output_dir(OUTPUT dist_dir) add_custom_command( TARGET ${arg_NAME} POST_BUILD diff --git a/Editor/Include/Editor/EditorApplication.h b/Editor/Include/Editor/EditorApplication.h index 69e4c5edc..947acc765 100644 --- a/Editor/Include/Editor/EditorApplication.h +++ b/Editor/Include/Editor/EditorApplication.h @@ -21,6 +21,7 @@ namespace Editor { EditorApplicationMode mode; std::string rhiType; bool gpuDebug; + bool softwareGpu; std::string projectRoot; }; diff --git a/Editor/Include/Editor/Frame/ProjectHubFrame.h b/Editor/Include/Editor/Frame/ProjectHubFrame.h index 07744a861..71f24b60e 100644 --- a/Editor/Include/Editor/Frame/ProjectHubFrame.h +++ b/Editor/Include/Editor/Frame/ProjectHubFrame.h @@ -40,14 +40,14 @@ namespace Editor { ProjectHubFrame(); ~ProjectHubFrame(); - void Render(EditorWindow& inWindow, const std::string& inRhiType, bool inGpuDebug); + void Render(EditorWindow& inWindow, const std::string& inRhiType, bool inGpuDebug, bool inSoftwareGpu); private: - void RenderActionBar(EditorWindow& inWindow, const std::string& inRhiType, bool inGpuDebug); - void RenderRecentProjects(EditorWindow& inWindow, const std::string& inRhiType, bool inGpuDebug); - void RenderCreateProjectPopup(EditorWindow& inWindow, const std::string& inRhiType, bool inGpuDebug); + void RenderActionBar(EditorWindow& inWindow, const std::string& inRhiType, bool inGpuDebug, bool inSoftwareGpu); + void RenderRecentProjects(EditorWindow& inWindow, const std::string& inRhiType, bool inGpuDebug, bool inSoftwareGpu); + void RenderCreateProjectPopup(EditorWindow& inWindow, const std::string& inRhiType, bool inGpuDebug, bool inSoftwareGpu); CreateProjectResult CreateProject(); - void OpenProject(EditorWindow& inWindow, const std::string& inProjectPath, const std::string& inRhiType, bool inGpuDebug); + void OpenProject(EditorWindow& inWindow, const std::string& inProjectPath, const std::string& inRhiType, bool inGpuDebug, bool inSoftwareGpu); void SaveRecentProjects() const; void TouchRecentProject(const std::string& inProjectPath); diff --git a/Editor/Src/EditorApplication.cpp b/Editor/Src/EditorApplication.cpp index 20308537d..6a30eef67 100644 --- a/Editor/Src/EditorApplication.cpp +++ b/Editor/Src/EditorApplication.cpp @@ -329,7 +329,7 @@ namespace Editor { void EditorApplication::RenderProjectHubFrame() { - projectHubFrame->Render(*window, desc.rhiType, desc.gpuDebug); + projectHubFrame->Render(*window, desc.rhiType, desc.gpuDebug, desc.softwareGpu); ImGui::Render(); Runtime::EngineHolder::Get().Tick(ImGui::GetIO().DeltaTime); window->RenderUiOnly(*ImGui::GetDrawData()); diff --git a/Editor/Src/EditorWindow.cpp b/Editor/Src/EditorWindow.cpp index 1efc4cfc7..45e535f38 100644 --- a/Editor/Src/EditorWindow.cpp +++ b/Editor/Src/EditorWindow.cpp @@ -459,7 +459,9 @@ namespace Editor { 0, RHI::TextureSubResourceInfo(), Common::UVec3Consts::zero, - Common::UVec3(static_cast(width), static_cast(height), 1))); + Common::UVec3(static_cast(width), static_cast(height), 1), + copyFootprint.rowPitch, + copyFootprint.slicePitch)); copyRecorder->ResourceBarrier(RHI::Barrier::Transition(imguiFontTexture.Get(), RHI::TextureState::copyDst, RHI::TextureState::shaderReadOnly)); copyRecorder->EndPass(); } diff --git a/Editor/Src/Frame/EditorFrame.cpp b/Editor/Src/Frame/EditorFrame.cpp index 9134b2149..af1139d4f 100644 --- a/Editor/Src/Frame/EditorFrame.cpp +++ b/Editor/Src/Frame/EditorFrame.cpp @@ -33,6 +33,11 @@ namespace Editor::Internal { : qualifiedName.substr(namespaceSeparator + 2); } + static bool IsComponentVisibleInDetails(const Mirror::Class& inClass) + { + return !inClass.HasMeta(Runtime::MetaPresets::editorHide); + } + static std::string EntityDisplayName(const Runtime::ECRegistry& inRegistry, Runtime::Entity inEntity) { const auto* name = inRegistry.Find(inEntity); @@ -69,7 +74,9 @@ namespace Editor { .log = true } { - std::erase_if(componentClasses, [](Runtime::CompClass clazz) -> bool { return !clazz->HasMeta("comp"); }); + std::erase_if(componentClasses, [](Runtime::CompClass clazz) -> bool { + return !clazz->HasMeta("comp") || !Internal::IsComponentVisibleInDetails(*clazz); + }); std::ranges::sort(componentClasses, [](Runtime::CompClass lhs, Runtime::CompClass rhs) -> bool { return lhs->GetName() < rhs->GetName(); }); @@ -253,6 +260,9 @@ namespace Editor { Runtime::CompClass componentToRemove = nullptr; inRegistry.CompEach(selectedEntity, [&](Runtime::CompClass compClass) -> void { + if (!Internal::IsComponentVisibleInDetails(*compClass)) { + return; + } ImGui::PushID(compClass->GetName().c_str()); const std::string componentName = Internal::ComponentDisplayName(*compClass); const std::string componentLabel = Widgets::Label(Icons::Tabler::boxMultiple, componentName); @@ -277,6 +287,9 @@ namespace Editor { ImGui::PopID(); }); inRegistry.TagEach(selectedEntity, [&](Runtime::TagClass tagClass) -> void { + if (!Internal::IsComponentVisibleInDetails(*tagClass)) { + return; + } ImGui::PushID(tagClass->GetName().c_str()); const std::string tagName = Internal::ComponentDisplayName(*tagClass); const std::string tagLabel = Widgets::Label(Icons::Tabler::box, tagName); diff --git a/Editor/Src/Frame/ProjectHubFrame.cpp b/Editor/Src/Frame/ProjectHubFrame.cpp index 39012c629..05de85b8a 100644 --- a/Editor/Src/Frame/ProjectHubFrame.cpp +++ b/Editor/Src/Frame/ProjectHubFrame.cpp @@ -89,13 +89,14 @@ namespace Editor::ProjectHub::Internal { return result; } - static std::string LaunchCommand(const std::string& inExecutable, const std::string& inProjectPath, const std::string& inRhiType, bool inGpuDebug) + static std::string LaunchCommand(const std::string& inExecutable, const std::string& inProjectPath, const std::string& inRhiType, bool inGpuDebug, bool inSoftwareGpu) { const std::string gpuDebugArg = inGpuDebug ? " -gpuDebug" : ""; + const std::string softwareGpuArg = inSoftwareGpu ? " -softwareGpu" : ""; #if PLATFORM_WINDOWS - return std::format("start \"\" \"{}\" -project \"{}\" -rhi {}{}", inExecutable, inProjectPath, inRhiType, gpuDebugArg); + return std::format("start \"\" \"{}\" -project \"{}\" -rhi {}{}{}", inExecutable, inProjectPath, inRhiType, gpuDebugArg, softwareGpuArg); #else - return std::format("\"{}\" -project \"{}\" -rhi {}{} &", inExecutable, inProjectPath, inRhiType, gpuDebugArg); + return std::format("\"{}\" -project \"{}\" -rhi {}{}{} &", inExecutable, inProjectPath, inRhiType, gpuDebugArg, softwareGpuArg); #endif } } @@ -126,7 +127,7 @@ namespace Editor { SaveRecentProjects(); } - void ProjectHubFrame::Render(EditorWindow& inWindow, const std::string& inRhiType, bool inGpuDebug) + void ProjectHubFrame::Render(EditorWindow& inWindow, const std::string& inRhiType, bool inGpuDebug, bool inSoftwareGpu) { const ImGuiViewport* viewport = ImGui::GetMainViewport(); ImGui::SetNextWindowPos(viewport->WorkPos); @@ -143,21 +144,21 @@ namespace Editor { | ImGuiWindowFlags_NoBringToFrontOnFocus); ImGui::PopStyleVar(2); - RenderActionBar(inWindow, inRhiType, inGpuDebug); + RenderActionBar(inWindow, inRhiType, inGpuDebug, inSoftwareGpu); ImGui::Dummy(ImVec2(0.0f, 22.0f)); - RenderRecentProjects(inWindow, inRhiType, inGpuDebug); - RenderCreateProjectPopup(inWindow, inRhiType, inGpuDebug); + RenderRecentProjects(inWindow, inRhiType, inGpuDebug, inSoftwareGpu); + RenderCreateProjectPopup(inWindow, inRhiType, inGpuDebug, inSoftwareGpu); ImGui::End(); } - void ProjectHubFrame::RenderActionBar(EditorWindow& inWindow, const std::string& inRhiType, bool inGpuDebug) + void ProjectHubFrame::RenderActionBar(EditorWindow& inWindow, const std::string& inRhiType, bool inGpuDebug, bool inSoftwareGpu) { const float spacing = ImGui::GetStyle().ItemSpacing.x; const float buttonWidth = (ImGui::GetContentRegionAvail().x - spacing) * 0.5f; const std::string openLabel = Widgets::Label(Icons::Tabler::folderOpen, "Open"); if (Widgets::PrimaryButton(openLabel.c_str(), ImVec2(buttonWidth, ProjectHub::Internal::actionButtonHeight))) { if (const auto selectedDirectory = PlatformUtils::SelectDirectory("Open Explosion Project")) { - OpenProject(inWindow, *selectedDirectory, inRhiType, inGpuDebug); + OpenProject(inWindow, *selectedDirectory, inRhiType, inGpuDebug, inSoftwareGpu); } } @@ -169,7 +170,7 @@ namespace Editor { } } - void ProjectHubFrame::RenderRecentProjects(EditorWindow& inWindow, const std::string& inRhiType, bool inGpuDebug) + void ProjectHubFrame::RenderRecentProjects(EditorWindow& inWindow, const std::string& inRhiType, bool inGpuDebug, bool inSoftwareGpu) { const std::string recentProjectsLabel = Widgets::Label(Icons::Tabler::folder, "Recent projects"); ImGui::TextUnformatted(recentProjectsLabel.c_str()); @@ -209,11 +210,11 @@ namespace Editor { ImGui::EndChild(); if (!projectToOpen.empty()) { - OpenProject(inWindow, projectToOpen, inRhiType, inGpuDebug); + OpenProject(inWindow, projectToOpen, inRhiType, inGpuDebug, inSoftwareGpu); } } - void ProjectHubFrame::RenderCreateProjectPopup(EditorWindow& inWindow, const std::string& inRhiType, bool inGpuDebug) + void ProjectHubFrame::RenderCreateProjectPopup(EditorWindow& inWindow, const std::string& inRhiType, bool inGpuDebug, bool inSoftwareGpu) { const ImGuiViewport* viewport = ImGui::GetMainViewport(); ImGui::SetNextWindowPos(viewport->GetCenter(), ImGuiCond_Appearing, ImVec2(0.5f, 0.5f)); @@ -288,7 +289,7 @@ namespace Editor { if (result.success) { statusMessage.clear(); ImGui::CloseCurrentPopup(); - OpenProject(inWindow, result.projectPath, inRhiType, inGpuDebug); + OpenProject(inWindow, result.projectPath, inRhiType, inGpuDebug, inSoftwareGpu); } else { statusMessage = result.error; } @@ -327,7 +328,7 @@ namespace Editor { return { .success = true, .error = {}, .projectPath = projectDir.String() }; } - void ProjectHubFrame::OpenProject(EditorWindow& inWindow, const std::string& inProjectPath, const std::string& inRhiType, bool inGpuDebug) + void ProjectHubFrame::OpenProject(EditorWindow& inWindow, const std::string& inProjectPath, const std::string& inRhiType, bool inGpuDebug, bool inSoftwareGpu) { const Common::Path projectDir(inProjectPath); if (!projectDir.Exists() || !projectDir.IsDirectory()) { @@ -338,7 +339,7 @@ namespace Editor { TouchRecentProject(inProjectPath); SaveRecentProjects(); - const std::string command = ProjectHub::Internal::LaunchCommand(Core::Paths::ExecutablePath().String(), inProjectPath, inRhiType, inGpuDebug); + const std::string command = ProjectHub::Internal::LaunchCommand(Core::Paths::ExecutablePath().String(), inProjectPath, inRhiType, inGpuDebug, inSoftwareGpu); std::ignore = std::system(command.c_str()); inWindow.RequestClose(); } diff --git a/Editor/Src/Main.cpp b/Editor/Src/Main.cpp index 93feab1d8..10e492669 100644 --- a/Editor/Src/Main.cpp +++ b/Editor/Src/Main.cpp @@ -19,6 +19,10 @@ static Core::CmdlineArgValue caGpuDebug( "gpuDebug", "-gpuDebug", false, "enable GPU validation layers"); +static Core::CmdlineArgValue caSoftwareGpu( + "softwareGpu", "-softwareGpu", false, + "prefer a software GPU, falling back to hardware when unavailable"); + static Editor::EditorApplicationMode GetAppMode() { return caProjectRoot.GetValue().empty() @@ -35,6 +39,7 @@ static void InitializeEngine() params.gameRoot = caProjectRoot.GetValue(); params.rhiType = caRhiType.GetValue(); params.gpuDebug = caGpuDebug.GetValue(); + params.useSoftwareGpu = caSoftwareGpu.GetValue(); Runtime::EngineHolder::Load("Editor", params); } @@ -47,6 +52,7 @@ int main(int argc, char* argv[]) .mode = GetAppMode(), .rhiType = caRhiType.GetValue(), .gpuDebug = caGpuDebug.GetValue(), + .softwareGpu = caSoftwareGpu.GetValue(), .projectRoot = caProjectRoot.GetValue() }; int result = 0; diff --git a/Engine/Source/Common/Include/Common/Concurrent.h b/Engine/Source/Common/Include/Common/Concurrent.h index 070c814c9..3f6295ff8 100644 --- a/Engine/Source/Common/Include/Common/Concurrent.h +++ b/Engine/Source/Common/Include/Common/Concurrent.h @@ -63,10 +63,8 @@ namespace Common { private: bool stop; - bool flush; std::mutex mutex; std::condition_variable taskCondition; - std::condition_variable flushCondition; NamedThread thread; std::queue> tasks; }; diff --git a/Engine/Source/Common/Include/Common/Process.h b/Engine/Source/Common/Include/Common/Process.h new file mode 100644 index 000000000..a77916f21 --- /dev/null +++ b/Engine/Source/Common/Include/Common/Process.h @@ -0,0 +1,13 @@ +#pragma once + +#include +#include +#include +#include + +namespace Common { + class Process { + public: + static std::optional Run(const std::string& inExecutablePath, const std::vector& inArguments); + }; +} diff --git a/Engine/Source/Common/Src/Concurrent.cpp b/Engine/Source/Common/Src/Concurrent.cpp index c0c3b0883..ef26a3ac6 100644 --- a/Engine/Source/Common/Src/Concurrent.cpp +++ b/Engine/Source/Common/Src/Concurrent.cpp @@ -71,27 +71,19 @@ namespace Common { WorkerThread::WorkerThread(const std::string& name) : stop(false) - , flush(false) { thread = NamedThread(name, [this]() -> void { while (true) { - bool needNotifyMainThread = false; std::vector> tasksToExecute; { std::unique_lock lock(mutex); - taskCondition.wait(lock, [this]() -> bool { return stop || flush || !tasks.empty(); }); + taskCondition.wait(lock, [this]() -> bool { return stop || !tasks.empty(); }); if (stop && tasks.empty()) { return; } - if (flush) { - tasksToExecute.reserve(tasks.size()); - while (!tasks.empty()) { - tasksToExecute.emplace_back(std::move(tasks.front())); - tasks.pop(); - } - flush = false; - needNotifyMainThread = true; - } else { + + tasksToExecute.reserve(tasks.size()); + while (!tasks.empty()) { tasksToExecute.emplace_back(std::move(tasks.front())); tasks.pop(); } @@ -99,9 +91,6 @@ namespace Common { for (auto& task : tasksToExecute) { task(); } - if (needNotifyMainThread) { - flushCondition.notify_one(); - } } }); } @@ -118,14 +107,7 @@ namespace Common { void WorkerThread::Flush() { - { - std::unique_lock lock(mutex); - flush = true; - } - taskCondition.notify_one(); - { - std::unique_lock lock(mutex); - flushCondition.wait(lock); - } + auto completion = EmplaceTask([]() -> void {}); + completion.wait(); } } diff --git a/Engine/Source/Common/Src/Process.cpp b/Engine/Source/Common/Src/Process.cpp new file mode 100644 index 000000000..791bd208c --- /dev/null +++ b/Engine/Source/Common/Src/Process.cpp @@ -0,0 +1,114 @@ +#include + +#if PLATFORM_WINDOWS +#include + +#include +#else +#include +#include +#include + +#if PLATFORM_MACOS +#include +#else +extern char** environ; +#endif +#endif + +namespace Common::Internal { +#if PLATFORM_WINDOWS + static void AppendWindowsCommandLineArgument(std::wstring& outCommandLine, const std::wstring& inArgument) + { + if (!outCommandLine.empty()) { + outCommandLine.push_back(L' '); + } + + outCommandLine.push_back(L'"'); + size_t backslashCount = 0; + for (const wchar_t character : inArgument) { + if (character == L'\\') { + ++backslashCount; + continue; + } + if (character == L'"') { + outCommandLine.append(backslashCount * 2 + 1, L'\\'); + } else { + outCommandLine.append(backslashCount, L'\\'); + } + backslashCount = 0; + outCommandLine.push_back(character); + } + outCommandLine.append(backslashCount * 2, L'\\'); + outCommandLine.push_back(L'"'); + } +#else + static char** GetEnvironment() + { +#if PLATFORM_MACOS + return *_NSGetEnviron(); +#else + return environ; +#endif + } + + static std::optional WaitForProcess(const pid_t processId) + { + int status = 0; + while (waitpid(processId, &status, 0) == -1) { + if (errno != EINTR) { + return std::nullopt; + } + } + if (WIFEXITED(status)) { + return WEXITSTATUS(status); + } + if (WIFSIGNALED(status)) { + return 128 + WTERMSIG(status); + } + return std::nullopt; + } +#endif +} + +namespace Common { + std::optional Process::Run(const std::string& inExecutablePath, const std::vector& inArguments) + { +#if PLATFORM_WINDOWS + const std::wstring executablePath = StringUtils::ToWideString(inExecutablePath); + std::wstring commandLine; + Internal::AppendWindowsCommandLineArgument(commandLine, executablePath); + for (const auto& argument : inArguments) { + Internal::AppendWindowsCommandLineArgument(commandLine, StringUtils::ToWideString(argument)); + } + + STARTUPINFOW startupInfo {}; + startupInfo.cb = sizeof(startupInfo); + PROCESS_INFORMATION processInfo {}; + if (!CreateProcessW(executablePath.c_str(), commandLine.data(), nullptr, nullptr, FALSE, 0, nullptr, nullptr, &startupInfo, &processInfo)) { + return std::nullopt; + } + + const DWORD waitResult = WaitForSingleObject(processInfo.hProcess, INFINITE); + DWORD exitCode = 0; + const bool hasExitCode = waitResult == WAIT_OBJECT_0 && GetExitCodeProcess(processInfo.hProcess, &exitCode); + CloseHandle(processInfo.hThread); + CloseHandle(processInfo.hProcess); + return hasExitCode ? std::optional(static_cast(exitCode)) : std::nullopt; +#else + std::vector arguments; + arguments.reserve(inArguments.size() + 2); + arguments.push_back(const_cast(inExecutablePath.c_str())); + for (const auto& argument : inArguments) { + arguments.push_back(const_cast(argument.c_str())); + } + arguments.push_back(nullptr); + + pid_t processId = 0; + if (posix_spawn(&processId, inExecutablePath.c_str(), nullptr, nullptr, arguments.data(), Internal::GetEnvironment()) != 0) { + return std::nullopt; + } + return Internal::WaitForProcess(processId); +#endif + } +} diff --git a/Engine/Source/Launch/Src/GameApplication.cpp b/Engine/Source/Launch/Src/GameApplication.cpp index dcb8e6ac4..4aca50897 100644 --- a/Engine/Source/Launch/Src/GameApplication.cpp +++ b/Engine/Source/Launch/Src/GameApplication.cpp @@ -17,6 +17,10 @@ namespace Launch { "rhiType", "-rhi", RHI::GetPlatformDefaultRHIAbbrString(), "rhi abbr string, can be 'dx12' or 'vulkan'"); + static Core::CmdlineArgValue caSoftwareGpu( + "softwareGpu", "-softwareGpu", false, + "prefer a software GPU, falling back to hardware when unavailable"); + GameApplication::GameApplication(int argc, char* argv[]) : lastFrameTimeSeconds(Common::TimePoint::Now().ToSeconds()) , thisFrameTimeSeconds(Common::TimePoint::Now().ToSeconds()) @@ -28,6 +32,7 @@ namespace Launch { engineInitParams.logToFile = true; engineInitParams.gpuDebug = false; engineInitParams.rhiType = caRhiType.GetValue(); + engineInitParams.useSoftwareGpu = caSoftwareGpu.GetValue(); Runtime::EngineHolder::Load(gameModuleName, engineInitParams); engine = &Runtime::EngineHolder::Get(); diff --git a/Engine/Source/RHI-DirectX12/Include/RHI/DirectX12/CommandRecorder.h b/Engine/Source/RHI-DirectX12/Include/RHI/DirectX12/CommandRecorder.h index b0fd0dc70..50a123052 100644 --- a/Engine/Source/RHI-DirectX12/Include/RHI/DirectX12/CommandRecorder.h +++ b/Engine/Source/RHI-DirectX12/Include/RHI/DirectX12/CommandRecorder.h @@ -104,7 +104,7 @@ namespace RHI::DirectX12 { void SetIndexBuffer(BufferView* inBufferView) override; void SetVertexBuffer(size_t inSlot, BufferView* inBufferView) override; void Draw(size_t inVertexCount, size_t inInstanceCount, size_t inFirstVertex, size_t inFirstInstance) override; - void DrawIndexed(size_t inIndexCount, size_t inInstanceCount, size_t inFirstIndex, size_t inBaseVertex, size_t inFirstInstance) override; + void DrawIndexed(size_t inIndexCount, size_t inInstanceCount, size_t inFirstIndex, int32_t inBaseVertex, size_t inFirstInstance) override; void SetViewport(float inX, float inY, float inWidth, float inHeight, float inMinDepth, float inMaxDepth) override; void SetScissor(uint32_t inLeft, uint32_t inTop, uint32_t inRight, uint32_t inBottom) override; void SetPrimitiveTopology(PrimitiveTopology inPrimitiveTopology) override; diff --git a/Engine/Source/RHI-DirectX12/Include/RHI/DirectX12/Common.h b/Engine/Source/RHI-DirectX12/Include/RHI/DirectX12/Common.h index dfda56ae0..aeea8bb09 100644 --- a/Engine/Source/RHI-DirectX12/Include/RHI/DirectX12/Common.h +++ b/Engine/Source/RHI-DirectX12/Include/RHI/DirectX12/Common.h @@ -18,6 +18,8 @@ namespace RHI::DirectX12 { DECLARE_EC_FUNC() DECLARE_FC_FUNC() + uint32_t GetDX12TexturePlaneSlice(TextureAspect aspect); + ECIMPL_BEGIN(QueueType, D3D12_COMMAND_LIST_TYPE) ECIMPL_ITEM(QueueType::graphics, D3D12_COMMAND_LIST_TYPE_DIRECT) ECIMPL_ITEM(QueueType::compute, D3D12_COMMAND_LIST_TYPE_COMPUTE) @@ -176,6 +178,7 @@ namespace RHI::DirectX12 { ECIMPL_ITEM(PrimitiveTopologyType::point, D3D12_PRIMITIVE_TOPOLOGY_TYPE_POINT) ECIMPL_ITEM(PrimitiveTopologyType::line, D3D12_PRIMITIVE_TOPOLOGY_TYPE_LINE) ECIMPL_ITEM(PrimitiveTopologyType::triangle, D3D12_PRIMITIVE_TOPOLOGY_TYPE_TRIANGLE) + ECIMPL_ITEM(PrimitiveTopologyType::patch, D3D12_PRIMITIVE_TOPOLOGY_TYPE_PATCH) ECIMPL_END(D3D12_PRIMITIVE_TOPOLOGY_TYPE) ECIMPL_BEGIN(FillMode, D3D12_FILL_MODE) @@ -239,6 +242,12 @@ namespace RHI::DirectX12 { ECIMPL_ITEM(IndexFormat::uint32, DXGI_FORMAT_R32_UINT) ECIMPL_END(DXGI_FORMAT) + ECIMPL_BEGIN(IndexFormat, D3D12_INDEX_BUFFER_STRIP_CUT_VALUE) + ECIMPL_ITEM(IndexFormat::uint16, D3D12_INDEX_BUFFER_STRIP_CUT_VALUE_0xFFFF) + ECIMPL_ITEM(IndexFormat::uint32, D3D12_INDEX_BUFFER_STRIP_CUT_VALUE_0xFFFFFFFF) + ECIMPL_ITEM(IndexFormat::max, D3D12_INDEX_BUFFER_STRIP_CUT_VALUE_DISABLED) + ECIMPL_END(D3D12_INDEX_BUFFER_STRIP_CUT_VALUE) + ECIMPL_BEGIN(VertexStepMode, D3D12_INPUT_CLASSIFICATION) ECIMPL_ITEM(VertexStepMode::perVertex, D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA) ECIMPL_ITEM(VertexStepMode::perInstance, D3D12_INPUT_CLASSIFICATION_PER_INSTANCE_DATA) diff --git a/Engine/Source/RHI-DirectX12/Include/RHI/DirectX12/Device.h b/Engine/Source/RHI-DirectX12/Include/RHI/DirectX12/Device.h index a4cdecdfc..b7231deda 100644 --- a/Engine/Source/RHI-DirectX12/Include/RHI/DirectX12/Device.h +++ b/Engine/Source/RHI-DirectX12/Include/RHI/DirectX12/Device.h @@ -94,7 +94,7 @@ namespace RHI::DirectX12 { Common::UniquePtr CreateQuerySet(const QuerySetCreateInfo& inCreateInfo) override; bool CheckSwapChainFormatSupport(Surface* inSurface, PixelFormat inFormat, ColorSpace inColorSpace) override; - TextureSubResourceCopyFootprint GetTextureSubResourceCopyFootprint(const Texture& texture, const TextureSubResourceInfo& subResourceInfo) override; + TextureSubResourceCopyFootprint GetTextureSubResourceCopyFootprint(const Texture& texture, const TextureSubResourceInfo& subResourceInfo, const Common::UVec3& copyRegion) override; ID3D12Device* GetNative() const; ID3D12CommandSignature* GetDrawIndirectCommandSignature() const; diff --git a/Engine/Source/RHI-DirectX12/Include/RHI/DirectX12/Instance.h b/Engine/Source/RHI-DirectX12/Include/RHI/DirectX12/Instance.h index c7e9a9fc6..bec182e5f 100644 --- a/Engine/Source/RHI-DirectX12/Include/RHI/DirectX12/Instance.h +++ b/Engine/Source/RHI-DirectX12/Include/RHI/DirectX12/Instance.h @@ -37,7 +37,6 @@ namespace RHI::DirectX12 { RHIType GetRHIType() override; uint32_t GetGpuNum() override; Gpu* GetGpu(uint32_t index) override; - void Destroy() override; IDXGIFactory4* GetNative() const; #if BUILD_CONFIG_DEBUG diff --git a/Engine/Source/RHI-DirectX12/Include/RHI/DirectX12/ShaderModule.h b/Engine/Source/RHI-DirectX12/Include/RHI/DirectX12/ShaderModule.h index 45e082e25..5e24ebd5b 100644 --- a/Engine/Source/RHI-DirectX12/Include/RHI/DirectX12/ShaderModule.h +++ b/Engine/Source/RHI-DirectX12/Include/RHI/DirectX12/ShaderModule.h @@ -17,10 +17,10 @@ namespace RHI::DirectX12 { const std::string& GetEntryPoint() override; - const D3D12_SHADER_BYTECODE& GetNative() const; + D3D12_SHADER_BYTECODE GetNative() const; private: - CD3DX12_SHADER_BYTECODE nativeShaderBytecode; + ShaderByteCodeRef byteCode; std::string entryPoint; }; } diff --git a/Engine/Source/RHI-DirectX12/Src/CommandRecorder.cpp b/Engine/Source/RHI-DirectX12/Src/CommandRecorder.cpp index 38ad5a9f4..1cecd0cc2 100644 --- a/Engine/Source/RHI-DirectX12/Src/CommandRecorder.cpp +++ b/Engine/Source/RHI-DirectX12/Src/CommandRecorder.cpp @@ -37,7 +37,7 @@ namespace RHI::DirectX12 { static size_t GetNativeSubResourceIndex(const DX12Texture& texture, const TextureSubResourceInfo& subResource) { const auto& createInfo = texture.GetCreateInfo(); - return D3D12CalcSubresource(subResource.mipLevel, subResource.arrayLayer, 0, createInfo.mipLevels, createInfo.type == TextureType::t3D ? 1 : createInfo.depthOrArraySize); + return D3D12CalcSubresource(subResource.mipLevel, subResource.arrayLayer, GetDX12TexturePlaneSlice(subResource.aspect), createInfo.mipLevels, createInfo.type == TextureType::t3D ? 1 : createInfo.depthOrArraySize); } static CD3DX12_TEXTURE_COPY_LOCATION GetNativeTextureCopyLocation(const DX12Texture& texture, const TextureSubResourceInfo& subResource) @@ -45,20 +45,24 @@ namespace RHI::DirectX12 { return { texture.GetNative(), static_cast(GetNativeSubResourceIndex(texture, subResource)) }; } - static CD3DX12_TEXTURE_COPY_LOCATION GetNativeBufferCopyLocationFromTextureLayout(DX12Device& device, const DX12Buffer& buffer, const DX12Texture& texture, const BufferTextureCopyInfo& copyInfo) + static CD3DX12_TEXTURE_COPY_LOCATION GetNativeBufferCopyLocationFromTextureLayout(DX12Device& device, const DX12Buffer& buffer, const DX12Texture& texture, const BufferTextureCopyInfo& copyInfo, const TextureAspect aspect, const size_t aspectIndex) { + Assert(aspect != TextureAspect::depthStencil); Assert(copyInfo.bufferOffset % D3D12_TEXTURE_DATA_PLACEMENT_ALIGNMENT == 0); - const auto aspectLayout = device.GetTextureSubResourceCopyFootprint(texture, copyInfo.textureSubResource); // NOLINT + Assert(copyInfo.bufferRowPitch % D3D12_TEXTURE_DATA_PITCH_ALIGNMENT == 0); - // The buffer is laid out as the full sub-resource footprint (so the slice stride is RowPitch * full height); - // the copied window is selected by the box passed to CopyTextureRegion, not by shrinking this footprint. + const auto planeBytes = copyInfo.bufferSlicePitch * copyInfo.copyRegion.z; + Assert(aspectIndex == 0 || planeBytes % D3D12_TEXTURE_DATA_PLACEMENT_ALIGNMENT == 0); + + const auto subResource = TextureSubResourceInfo(copyInfo.textureSubResource.mipLevel, copyInfo.textureSubResource.arrayLayer, aspect); + const auto nativeResourceDesc = texture.GetNative()->GetDesc(); D3D12_PLACED_SUBRESOURCE_FOOTPRINT bufferLayout; - bufferLayout.Offset = copyInfo.bufferOffset; - bufferLayout.Footprint.Format = texture.GetNative()->GetDesc().Format; - bufferLayout.Footprint.Width = aspectLayout.extent.x; - bufferLayout.Footprint.Height = aspectLayout.extent.y; - bufferLayout.Footprint.Depth = aspectLayout.extent.z; - bufferLayout.Footprint.RowPitch = aspectLayout.rowPitch; + device.GetNative()->GetCopyableFootprints(&nativeResourceDesc, static_cast(GetNativeSubResourceIndex(texture, subResource)), 1, copyInfo.bufferOffset + planeBytes * aspectIndex, &bufferLayout, nullptr, nullptr, nullptr); + Assert(bufferLayout.Offset == copyInfo.bufferOffset + planeBytes * aspectIndex); + bufferLayout.Footprint.Width = copyInfo.copyRegion.x; + bufferLayout.Footprint.Height = static_cast(copyInfo.bufferSlicePitch / copyInfo.bufferRowPitch); + bufferLayout.Footprint.Depth = copyInfo.copyRegion.z; + bufferLayout.Footprint.RowPitch = static_cast(copyInfo.bufferRowPitch); return { buffer.GetNative(), bufferLayout }; } @@ -122,17 +126,22 @@ namespace RHI::DirectX12 { const auto* srcBuffer = static_cast(src); const auto* dstTexture = static_cast(dst); - const CD3DX12_TEXTURE_COPY_LOCATION srcCopyRegion = GetNativeBufferCopyLocationFromTextureLayout(device, *srcBuffer, *dstTexture, copyInfo); - const CD3DX12_TEXTURE_COPY_LOCATION dstCopyRegion = GetNativeTextureCopyLocation(*dstTexture, copyInfo.textureSubResource); + RHI::Internal::ValidateBufferTextureCopy(*src, *dst, copyInfo); const D3D12_BOX srcBox = GetNativeBox(Common::UVec3Consts::zero, copyInfo.copyRegion); - - commandBuffer.GetNativeCmdList()->CopyTextureRegion( - &dstCopyRegion, - copyInfo.textureOrigin.x, - copyInfo.textureOrigin.y, - copyInfo.textureOrigin.z, - &srcCopyRegion, - &srcBox); + const auto aspects = GetTextureAspectComponents(copyInfo.textureSubResource.aspect); + for (size_t aspectIndex = 0; aspectIndex < aspects.size(); aspectIndex++) { + const auto subResource = TextureSubResourceInfo(copyInfo.textureSubResource.mipLevel, copyInfo.textureSubResource.arrayLayer, aspects[aspectIndex]); + const CD3DX12_TEXTURE_COPY_LOCATION srcCopyRegion = GetNativeBufferCopyLocationFromTextureLayout(device, *srcBuffer, *dstTexture, copyInfo, aspects[aspectIndex], aspectIndex); + const CD3DX12_TEXTURE_COPY_LOCATION dstCopyRegion = GetNativeTextureCopyLocation(*dstTexture, subResource); + + commandBuffer.GetNativeCmdList()->CopyTextureRegion( + &dstCopyRegion, + copyInfo.textureOrigin.x, + copyInfo.textureOrigin.y, + copyInfo.textureOrigin.z, + &srcCopyRegion, + &srcBox); + } } void DX12CopyPassCommandRecorder::CopyTextureToBuffer(Texture* src, Buffer* dst, const BufferTextureCopyInfo& copyInfo) @@ -140,15 +149,20 @@ namespace RHI::DirectX12 { const auto* srcTexture = static_cast(src); const auto* dstBuffer = static_cast(dst); - const CD3DX12_TEXTURE_COPY_LOCATION srcCopyRegion = GetNativeTextureCopyLocation(*srcTexture, copyInfo.textureSubResource); - const CD3DX12_TEXTURE_COPY_LOCATION dstCopyRegion = GetNativeBufferCopyLocationFromTextureLayout(device, *dstBuffer, *srcTexture, copyInfo); + RHI::Internal::ValidateBufferTextureCopy(*dst, *src, copyInfo); const D3D12_BOX srcBox = GetNativeBox(copyInfo.textureOrigin, copyInfo.copyRegion); - - commandBuffer.GetNativeCmdList()->CopyTextureRegion( - &dstCopyRegion, - 0, 0, 0, - &srcCopyRegion, - &srcBox); + const auto aspects = GetTextureAspectComponents(copyInfo.textureSubResource.aspect); + for (size_t aspectIndex = 0; aspectIndex < aspects.size(); aspectIndex++) { + const auto subResource = TextureSubResourceInfo(copyInfo.textureSubResource.mipLevel, copyInfo.textureSubResource.arrayLayer, aspects[aspectIndex]); + const CD3DX12_TEXTURE_COPY_LOCATION srcCopyRegion = GetNativeTextureCopyLocation(*srcTexture, subResource); + const CD3DX12_TEXTURE_COPY_LOCATION dstCopyRegion = GetNativeBufferCopyLocationFromTextureLayout(device, *dstBuffer, *srcTexture, copyInfo, aspects[aspectIndex], aspectIndex); + + commandBuffer.GetNativeCmdList()->CopyTextureRegion( + &dstCopyRegion, + 0, 0, 0, + &srcCopyRegion, + &srcBox); + } } void DX12CopyPassCommandRecorder::CopyTextureToTexture(Texture* src, Texture* dst, const TextureCopyInfo& copyInfo) @@ -156,17 +170,25 @@ namespace RHI::DirectX12 { const auto* srcTexture = static_cast(src); const auto* dstTexture = static_cast(dst); - const CD3DX12_TEXTURE_COPY_LOCATION srcCopyRegion = GetNativeTextureCopyLocation(*srcTexture, copyInfo.srcSubResource); - const CD3DX12_TEXTURE_COPY_LOCATION dstCopyRegion = GetNativeTextureCopyLocation(*dstTexture, copyInfo.dstSubResource); const D3D12_BOX srcBox = GetNativeBox(copyInfo.srcOrigin, copyInfo.copyRegion); - - commandBuffer.GetNativeCmdList()->CopyTextureRegion( - &dstCopyRegion, - copyInfo.dstOrigin.x, - copyInfo.dstOrigin.y, - copyInfo.dstOrigin.z, - &srcCopyRegion, - &srcBox); + const auto srcAspects = GetTextureAspectComponents(copyInfo.srcSubResource.aspect); + const auto dstAspects = GetTextureAspectComponents(copyInfo.dstSubResource.aspect); + Assert(srcAspects.size() == dstAspects.size()); + for (size_t aspectIndex = 0; aspectIndex < srcAspects.size(); aspectIndex++) { + Assert(srcAspects[aspectIndex] == dstAspects[aspectIndex]); + const auto srcSubResource = TextureSubResourceInfo(copyInfo.srcSubResource.mipLevel, copyInfo.srcSubResource.arrayLayer, srcAspects[aspectIndex]); + const auto dstSubResource = TextureSubResourceInfo(copyInfo.dstSubResource.mipLevel, copyInfo.dstSubResource.arrayLayer, dstAspects[aspectIndex]); + const CD3DX12_TEXTURE_COPY_LOCATION srcCopyRegion = GetNativeTextureCopyLocation(*srcTexture, srcSubResource); + const CD3DX12_TEXTURE_COPY_LOCATION dstCopyRegion = GetNativeTextureCopyLocation(*dstTexture, dstSubResource); + + commandBuffer.GetNativeCmdList()->CopyTextureRegion( + &dstCopyRegion, + copyInfo.dstOrigin.x, + copyInfo.dstOrigin.y, + copyInfo.dstOrigin.z, + &srcCopyRegion, + &srcBox); + } } void DX12CopyPassCommandRecorder::EndPass() @@ -369,7 +391,7 @@ namespace RHI::DirectX12 { commandBuffer.GetNativeCmdList()->DrawInstanced(inVertexCount, inInstanceCount, inFirstVertex, inFirstInstance); } - void DX12RasterPassCommandRecorder::DrawIndexed(const size_t inIndexCount, const size_t inInstanceCount, const size_t inFirstIndex, const size_t inBaseVertex, const size_t inFirstInstance) + void DX12RasterPassCommandRecorder::DrawIndexed(const size_t inIndexCount, const size_t inInstanceCount, const size_t inFirstIndex, const int32_t inBaseVertex, const size_t inFirstInstance) { commandBuffer.GetNativeCmdList()->DrawIndexedInstanced(inIndexCount, inInstanceCount, inFirstIndex, inBaseVertex, inFirstInstance); } @@ -394,6 +416,14 @@ namespace RHI::DirectX12 { void DX12RasterPassCommandRecorder::SetPrimitiveTopology(PrimitiveTopology inPrimitiveTopology) { + if (inPrimitiveTopology == PrimitiveTopology::patchList) { + AssertWithReason(rasterPipeline != nullptr, "a raster pipeline must be bound before setting patch-list topology"); + const uint32_t patchControlPoints = rasterPipeline->GetPrimitiveState().patchControlPoints; + AssertWithReason(patchControlPoints >= 1 && patchControlPoints <= 32, "patch-list topology requires a tessellation pipeline"); + const auto nativeTopology = static_cast(D3D_PRIMITIVE_TOPOLOGY_1_CONTROL_POINT_PATCHLIST + patchControlPoints - 1); + commandBuffer.GetNativeCmdList()->IASetPrimitiveTopology(nativeTopology); + return; + } commandBuffer.GetNativeCmdList()->IASetPrimitiveTopology(EnumCast(inPrimitiveTopology)); } diff --git a/Engine/Source/RHI-DirectX12/Src/Common.cpp b/Engine/Source/RHI-DirectX12/Src/Common.cpp index 159c57e50..75b6ae6a6 100644 --- a/Engine/Source/RHI-DirectX12/Src/Common.cpp +++ b/Engine/Source/RHI-DirectX12/Src/Common.cpp @@ -5,5 +5,16 @@ #include namespace RHI::DirectX12 { - + uint32_t GetDX12TexturePlaneSlice(const TextureAspect aspect) + { + switch (aspect) { + case TextureAspect::color: + case TextureAspect::depth: + return 0; + case TextureAspect::stencil: + return 1; + default: + return Assert(false), 0; + } + } } diff --git a/Engine/Source/RHI-DirectX12/Src/DX12RHIModule.cpp b/Engine/Source/RHI-DirectX12/Src/DX12RHIModule.cpp index 884413c67..bcabf420b 100644 --- a/Engine/Source/RHI-DirectX12/Src/DX12RHIModule.cpp +++ b/Engine/Source/RHI-DirectX12/Src/DX12RHIModule.cpp @@ -17,7 +17,9 @@ namespace RHI::DirectX12 { void DX12RHIModule::OnUnload() { - delete gInstance; + auto* instance = gInstance; + gInstance = nullptr; + delete instance; } Core::ModuleType DX12RHIModule::Type() const diff --git a/Engine/Source/RHI-DirectX12/Src/Device.cpp b/Engine/Source/RHI-DirectX12/Src/Device.cpp index 11036fb98..e6f824206 100644 --- a/Engine/Source/RHI-DirectX12/Src/Device.cpp +++ b/Engine/Source/RHI-DirectX12/Src/Device.cpp @@ -2,6 +2,7 @@ // Created by johnk on 15/1/2022. // +#include #include #include @@ -280,24 +281,36 @@ namespace RHI::DirectX12 { return iter != supportedFormats.end() && iter->second.contains(inFormat); } - TextureSubResourceCopyFootprint DX12Device::GetTextureSubResourceCopyFootprint(const Texture& texture, const TextureSubResourceInfo& subResourceInfo) + TextureSubResourceCopyFootprint DX12Device::GetTextureSubResourceCopyFootprint(const Texture& texture, const TextureSubResourceInfo& subResourceInfo, const Common::UVec3& copyRegion) { const auto& dx12Texture = static_cast(texture); const auto createInfo = texture.GetCreateInfo(); const auto nativeResourceDesc = dx12Texture.GetNative()->GetDesc(); const auto arraySize = createInfo.type == TextureType::t3D ? 1 : createInfo.depthOrArraySize; - const size_t nativeSubResourceIndex = D3D12CalcSubresource(subResourceInfo.mipLevel, subResourceInfo.arrayLayer, 0, createInfo.mipLevels, arraySize); + const auto aspects = GetTextureAspectComponents(subResourceInfo.aspect); + const size_t nativeSubResourceIndex = D3D12CalcSubresource(subResourceInfo.mipLevel, subResourceInfo.arrayLayer, GetDX12TexturePlaneSlice(aspects.front()), createInfo.mipLevels, arraySize); D3D12_PLACED_SUBRESOURCE_FOOTPRINT footprint; nativeDevice->GetCopyableFootprints(&nativeResourceDesc, nativeSubResourceIndex, 1, 0, &footprint, nullptr, nullptr, nullptr); + const Common::UVec3 subResourceExtent = { footprint.Footprint.Width, footprint.Footprint.Height, footprint.Footprint.Depth }; + const auto useFullSubResource = copyRegion == Common::UVec3Consts::zero; + const auto extent = useFullSubResource ? subResourceExtent : copyRegion; + Assert(extent.x <= subResourceExtent.x && extent.y <= subResourceExtent.y && extent.z <= subResourceExtent.z); + TextureSubResourceCopyFootprint result {}; - result.extent = { footprint.Footprint.Width, footprint.Footprint.Height, footprint.Footprint.Depth }; - result.bytesPerPixel = GetBytesPerPixel(createInfo.format); - result.rowPitch = footprint.Footprint.RowPitch; - result.slicePitch = footprint.Footprint.RowPitch * footprint.Footprint.Height; - result.totalBytes = footprint.Footprint.RowPitch * footprint.Footprint.Height * footprint.Footprint.Depth; + result.extent = extent; + result.bytesPerPixel = 0; + for (const auto aspect : aspects) { + result.bytesPerPixel = std::max(result.bytesPerPixel, GetTextureAspectBytesPerPixel(createInfo.format, aspect)); + } + result.rowPitch = Common::AlignUp(result.bytesPerPixel * result.extent.x, static_cast(D3D12_TEXTURE_DATA_PITCH_ALIGNMENT)); + result.slicePitch = result.rowPitch * result.extent.y; + if (aspects.size() > 1 && result.slicePitch % D3D12_TEXTURE_DATA_PLACEMENT_ALIGNMENT != 0) { + result.slicePitch += result.rowPitch; + } + result.totalBytes = result.slicePitch * result.extent.z * aspects.size(); return result; } diff --git a/Engine/Source/RHI-DirectX12/Src/Gpu.cpp b/Engine/Source/RHI-DirectX12/Src/Gpu.cpp index 363395b27..ab3c206b4 100644 --- a/Engine/Source/RHI-DirectX12/Src/Gpu.cpp +++ b/Engine/Source/RHI-DirectX12/Src/Gpu.cpp @@ -6,6 +6,8 @@ #include #include +#include + namespace RHI::DirectX12 { DX12Gpu::DX12Gpu(DX12Instance& inInstance, ComPtr&& inNativeAdapter) : instance(inInstance) @@ -21,8 +23,11 @@ namespace RHI::DirectX12 { Assert(SUCCEEDED(nativeAdapter->GetDesc1(&desc))); GpuProperty property {}; + property.name = Common::StringUtils::ToByteString(desc.Description); property.vendorId = desc.VendorId; property.deviceId = desc.DeviceId; + property.dedicatedVideoMemorySize = desc.DedicatedVideoMemory; + property.sharedSystemMemorySize = desc.SharedSystemMemory; property.type = desc.Flags & DXGI_ADAPTER_FLAG_SOFTWARE ? GpuType::software : GpuType::hardware; return property; } @@ -34,7 +39,9 @@ namespace RHI::DirectX12 { | FeatureBits::timestampQuery | FeatureBits::multiDrawIndirect | FeatureBits::drawIndirectFirstInstance - | FeatureBits::textureCubeArray; + | FeatureBits::textureCubeArray + | FeatureBits::geometryShader + | FeatureBits::tessellationShader; } GpuLimits DX12Gpu::GetLimits() diff --git a/Engine/Source/RHI-DirectX12/Src/Instance.cpp b/Engine/Source/RHI-DirectX12/Src/Instance.cpp index a1e75c9af..b41c34681 100644 --- a/Engine/Source/RHI-DirectX12/Src/Instance.cpp +++ b/Engine/Source/RHI-DirectX12/Src/Instance.cpp @@ -110,9 +110,22 @@ namespace RHI::DirectX12 { void DX12Instance::EnumerateAdapters() { + if (GetCreateInfo().useSoftwareGpu) { + ComPtr nativeWarpAdapter; + Assert(SUCCEEDED(nativeFactory->EnumWarpAdapter(IID_PPV_ARGS(&nativeWarpAdapter)))); + ComPtr nativeWarpAdapter1; + Assert(SUCCEEDED(nativeWarpAdapter.As(&nativeWarpAdapter1))); + gpus.emplace_back(Common::MakeUnique(*this, std::move(nativeWarpAdapter1))); + return; + } + ComPtr tempAdapter; for (uint32_t i = 0; SUCCEEDED(nativeFactory->EnumAdapters1(i, &tempAdapter)); i++) { - gpus.emplace_back(Common::MakeUnique(*this, std::move(tempAdapter))); + DXGI_ADAPTER_DESC1 desc; + Assert(SUCCEEDED(tempAdapter->GetDesc1(&desc))); + if ((desc.Flags & DXGI_ADAPTER_FLAG_SOFTWARE) == 0) { + gpus.emplace_back(Common::MakeUnique(*this, std::move(tempAdapter))); + } tempAdapter = nullptr; } } @@ -127,8 +140,4 @@ namespace RHI::DirectX12 { return gpus[index].Get(); } - void DX12Instance::Destroy() - { - delete this; - } } diff --git a/Engine/Source/RHI-DirectX12/Src/Pipeline.cpp b/Engine/Source/RHI-DirectX12/Src/Pipeline.cpp index d5cd0d594..606ffadf4 100644 --- a/Engine/Source/RHI-DirectX12/Src/Pipeline.cpp +++ b/Engine/Source/RHI-DirectX12/Src/Pipeline.cpp @@ -211,17 +211,24 @@ namespace RHI::DirectX12 { { auto* vertexShader = static_cast(inCreateInfo.vertexShader); auto* fragmentShader = static_cast(inCreateInfo.pixelShader); + auto* geometryShader = static_cast(inCreateInfo.geometryShader); + auto* hullShader = static_cast(inCreateInfo.hullShader); + auto* domainShader = static_cast(inCreateInfo.domainShader); D3D12_GRAPHICS_PIPELINE_STATE_DESC desc {}; desc.pRootSignature = pipelineLayout->GetNative(); desc.VS = vertexShader->GetNative(); desc.PS = fragmentShader->GetNative(); + desc.GS = geometryShader != nullptr ? geometryShader->GetNative() : D3D12_SHADER_BYTECODE {}; + desc.HS = hullShader != nullptr ? hullShader->GetNative() : D3D12_SHADER_BYTECODE {}; + desc.DS = domainShader != nullptr ? domainShader->GetNative() : D3D12_SHADER_BYTECODE {}; desc.RasterizerState = GetDX12RasterizerDesc(inCreateInfo); desc.BlendState = GetDX12BlendDesc(inCreateInfo); desc.DepthStencilState = GetDX12DepthStencilDesc(inCreateInfo); desc.SampleMask = GetDX12SampleMask(inCreateInfo); desc.SampleDesc = GetDX12SampleDesc(inCreateInfo); desc.PrimitiveTopologyType = EnumCast(inCreateInfo.primitiveState.topologyType); + desc.IBStripCutValue = EnumCast(inCreateInfo.primitiveState.stripIndexFormat); UpdateDX12RenderTargetsDesc(desc, inCreateInfo); UpdateDX12DepthStencilTargetDesc(desc, inCreateInfo); auto inputElements = GetDX12InputElements(inCreateInfo); diff --git a/Engine/Source/RHI-DirectX12/Src/ShaderModule.cpp b/Engine/Source/RHI-DirectX12/Src/ShaderModule.cpp index 20d94d767..9db25c6ec 100644 --- a/Engine/Source/RHI-DirectX12/Src/ShaderModule.cpp +++ b/Engine/Source/RHI-DirectX12/Src/ShaderModule.cpp @@ -7,9 +7,10 @@ namespace RHI::DirectX12 { DX12ShaderModule::DX12ShaderModule(const ShaderModuleCreateInfo& inCreateInfo) : ShaderModule(inCreateInfo) - , nativeShaderBytecode(inCreateInfo.byteCode, inCreateInfo.size) + , byteCode(inCreateInfo.byteCode) , entryPoint(inCreateInfo.entryPoint) { + Assert(byteCode != nullptr); } DX12ShaderModule::~DX12ShaderModule() = default; @@ -19,8 +20,8 @@ namespace RHI::DirectX12 { return entryPoint; } - const D3D12_SHADER_BYTECODE& DX12ShaderModule::GetNative() const + D3D12_SHADER_BYTECODE DX12ShaderModule::GetNative() const { - return nativeShaderBytecode; + return CD3DX12_SHADER_BYTECODE(byteCode->GetData(), byteCode->GetSize()); } } diff --git a/Engine/Source/RHI-Dummy/Include/RHI/Dummy/CommandRecorder.h b/Engine/Source/RHI-Dummy/Include/RHI/Dummy/CommandRecorder.h index a4687454c..35d874af1 100644 --- a/Engine/Source/RHI-Dummy/Include/RHI/Dummy/CommandRecorder.h +++ b/Engine/Source/RHI-Dummy/Include/RHI/Dummy/CommandRecorder.h @@ -87,7 +87,7 @@ namespace RHI::Dummy { void SetIndexBuffer(BufferView* bufferView) override; void SetVertexBuffer(size_t slot, BufferView* bufferView) override; void Draw(size_t vertexCount, size_t instanceCount, size_t firstVertex, size_t firstInstance) override; - void DrawIndexed(size_t indexCount, size_t instanceCount, size_t firstIndex, size_t baseVertex, size_t firstInstance) override; + void DrawIndexed(size_t indexCount, size_t instanceCount, size_t firstIndex, int32_t baseVertex, size_t firstInstance) override; void SetViewport(float topLeftX, float topLeftY, float width, float height, float minDepth, float maxDepth) override; void SetScissor(uint32_t left, uint32_t top, uint32_t right, uint32_t bottom) override; void SetPrimitiveTopology(PrimitiveTopology primitiveTopology) override; diff --git a/Engine/Source/RHI-Dummy/Include/RHI/Dummy/Device.h b/Engine/Source/RHI-Dummy/Include/RHI/Dummy/Device.h index 4d28be60e..459922201 100644 --- a/Engine/Source/RHI-Dummy/Include/RHI/Dummy/Device.h +++ b/Engine/Source/RHI-Dummy/Include/RHI/Dummy/Device.h @@ -37,7 +37,7 @@ namespace RHI::Dummy { Common::UniquePtr CreateQuerySet(const QuerySetCreateInfo& createInfo) override; bool CheckSwapChainFormatSupport(Surface *surface, PixelFormat format, ColorSpace colorSpace) override; - TextureSubResourceCopyFootprint GetTextureSubResourceCopyFootprint(const Texture& texture, const TextureSubResourceInfo& subResourceInfo) override; + TextureSubResourceCopyFootprint GetTextureSubResourceCopyFootprint(const Texture& texture, const TextureSubResourceInfo& subResourceInfo, const Common::UVec3& copyRegion) override; private: DummyGpu& gpu; diff --git a/Engine/Source/RHI-Dummy/Include/RHI/Dummy/Instance.h b/Engine/Source/RHI-Dummy/Include/RHI/Dummy/Instance.h index dec5fc43a..f5cab0468 100644 --- a/Engine/Source/RHI-Dummy/Include/RHI/Dummy/Instance.h +++ b/Engine/Source/RHI-Dummy/Include/RHI/Dummy/Instance.h @@ -19,7 +19,6 @@ namespace RHI::Dummy { RHIType GetRHIType() override; uint32_t GetGpuNum() override; Gpu* GetGpu(uint32_t index) override; - void Destroy() override; private: Common::UniquePtr dummyGpu; diff --git a/Engine/Source/RHI-Dummy/Src/CommandRecorder.cpp b/Engine/Source/RHI-Dummy/Src/CommandRecorder.cpp index ada6bbdb2..92d44b98b 100644 --- a/Engine/Source/RHI-Dummy/Src/CommandRecorder.cpp +++ b/Engine/Source/RHI-Dummy/Src/CommandRecorder.cpp @@ -31,10 +31,12 @@ namespace RHI::Dummy { void DummyCopyPassCommandRecorder::CopyBufferToTexture(Buffer* src, Texture* dst, const BufferTextureCopyInfo& copyInfo) { + RHI::Internal::ValidateBufferTextureCopy(*src, *dst, copyInfo); } void DummyCopyPassCommandRecorder::CopyTextureToBuffer(Texture* src, Buffer* dst, const BufferTextureCopyInfo& copyInfo) { + RHI::Internal::ValidateBufferTextureCopy(*dst, *src, copyInfo); } void DummyCopyPassCommandRecorder::CopyTextureToTexture(Texture* src, Texture* dst, const TextureCopyInfo& copyInfo) @@ -129,7 +131,7 @@ namespace RHI::Dummy { { } - void DummyRasterPassCommandRecorder::DrawIndexed(size_t indexCount, size_t instanceCount, size_t firstIndex, size_t baseVertex, size_t firstInstance) + void DummyRasterPassCommandRecorder::DrawIndexed(size_t indexCount, size_t instanceCount, size_t firstIndex, int32_t baseVertex, size_t firstInstance) { } diff --git a/Engine/Source/RHI-Dummy/Src/Device.cpp b/Engine/Source/RHI-Dummy/Src/Device.cpp index 9df125dd8..77f0e6928 100644 --- a/Engine/Source/RHI-Dummy/Src/Device.cpp +++ b/Engine/Source/RHI-Dummy/Src/Device.cpp @@ -2,6 +2,8 @@ // Created by johnk on 2023/3/21. // +#include + #include #include #include @@ -132,8 +134,29 @@ namespace RHI::Dummy { return true; } - TextureSubResourceCopyFootprint DummyDevice::GetTextureSubResourceCopyFootprint(const Texture& texture, const TextureSubResourceInfo& subResourceInfo) - { - return {}; + TextureSubResourceCopyFootprint DummyDevice::GetTextureSubResourceCopyFootprint(const Texture& texture, const TextureSubResourceInfo& subResourceInfo, const Common::UVec3& copyRegion) + { + const auto& createInfo = texture.GetCreateInfo(); + const auto mipLevel = subResourceInfo.mipLevel; + const auto baseDepth = createInfo.type == TextureType::t3D ? createInfo.depthOrArraySize : 1; + const Common::UVec3 subResourceExtent = { + std::max(createInfo.width >> mipLevel, 1u), + std::max(createInfo.height >> mipLevel, 1u), + std::max(baseDepth >> mipLevel, 1u) + }; + const auto extent = copyRegion == Common::UVec3Consts::zero ? subResourceExtent : copyRegion; + Assert(extent.x <= subResourceExtent.x && extent.y <= subResourceExtent.y && extent.z <= subResourceExtent.z); + + TextureSubResourceCopyFootprint result {}; + result.extent = extent; + const auto aspects = GetTextureAspectComponents(subResourceInfo.aspect); + result.bytesPerPixel = 0; + for (const auto aspect : aspects) { + result.bytesPerPixel = std::max(result.bytesPerPixel, GetTextureAspectBytesPerPixel(createInfo.format, aspect)); + } + result.rowPitch = result.bytesPerPixel * result.extent.x; + result.slicePitch = result.rowPitch * result.extent.y; + result.totalBytes = result.slicePitch * result.extent.z * aspects.size(); + return result; } } diff --git a/Engine/Source/RHI-Dummy/Src/DummyRHIModule.cpp b/Engine/Source/RHI-Dummy/Src/DummyRHIModule.cpp index d4fea3921..dd54106f0 100644 --- a/Engine/Source/RHI-Dummy/Src/DummyRHIModule.cpp +++ b/Engine/Source/RHI-Dummy/Src/DummyRHIModule.cpp @@ -17,7 +17,9 @@ namespace RHI::Dummy { void DummyRHIModule::OnUnload() { - delete gInstance; + auto* instance = gInstance; + gInstance = nullptr; + delete instance; } Core::ModuleType DummyRHIModule::Type() const diff --git a/Engine/Source/RHI-Dummy/Src/Gpu.cpp b/Engine/Source/RHI-Dummy/Src/Gpu.cpp index d02abc004..c154bc0e0 100644 --- a/Engine/Source/RHI-Dummy/Src/Gpu.cpp +++ b/Engine/Source/RHI-Dummy/Src/Gpu.cpp @@ -15,7 +15,10 @@ namespace RHI::Dummy { GpuProperty DummyGpu::GetProperty() { - return {}; + GpuProperty result {}; + result.name = "Dummy GPU"; + result.type = GpuType::software; + return result; } FeatureFlags DummyGpu::GetFeatures() diff --git a/Engine/Source/RHI-Dummy/Src/Instance.cpp b/Engine/Source/RHI-Dummy/Src/Instance.cpp index f90587366..291eac6fa 100644 --- a/Engine/Source/RHI-Dummy/Src/Instance.cpp +++ b/Engine/Source/RHI-Dummy/Src/Instance.cpp @@ -33,8 +33,4 @@ namespace RHI::Dummy { return dummyGpu.Get(); } - void DummyInstance::Destroy() - { - delete this; - } } diff --git a/Engine/Source/RHI-Vulkan/CMakeLists.txt b/Engine/Source/RHI-Vulkan/CMakeLists.txt index b872d14cf..b350e418b 100644 --- a/Engine/Source/RHI-Vulkan/CMakeLists.txt +++ b/Engine/Source/RHI-Vulkan/CMakeLists.txt @@ -12,7 +12,7 @@ exp_add_library( TYPE SHARED SRC ${sources} ${platform_sources} PUBLIC_INC Include - PUBLIC_LIB RHI ${platform_ext_libs} Vulkan::Headers Vulkan::Loader vulkan-validationlayers::vulkan-validationlayers spirv-cross::spirv-cross GPUOpen::VulkanMemoryAllocator + PUBLIC_LIB RHI ${platform_ext_libs} swiftshader::swiftshader Vulkan::Headers Vulkan::Loader vulkan-validationlayers::vulkan-validationlayers spirv-cross::spirv-cross GPUOpen::VulkanMemoryAllocator ) # .mm files can not perform unity build with .cpp files diff --git a/Engine/Source/RHI-Vulkan/Include/RHI/Vulkan/CommandRecorder.h b/Engine/Source/RHI-Vulkan/Include/RHI/Vulkan/CommandRecorder.h index ff8a83fcf..6ef90401c 100644 --- a/Engine/Source/RHI-Vulkan/Include/RHI/Vulkan/CommandRecorder.h +++ b/Engine/Source/RHI-Vulkan/Include/RHI/Vulkan/CommandRecorder.h @@ -106,7 +106,7 @@ namespace RHI::Vulkan { void SetIndexBuffer(BufferView* inBufferView) override; void SetVertexBuffer(size_t inSlot, BufferView* inBufferView) override; void Draw(size_t inVertexCount, size_t inInstanceCount, size_t inFirstVertex, size_t inFirstInstance) override; - void DrawIndexed(size_t inIndexCount, size_t inInstanceCount, size_t inFirstIndex, size_t inBaseVertex, size_t inFirstInstance) override; + void DrawIndexed(size_t inIndexCount, size_t inInstanceCount, size_t inFirstIndex, int32_t inBaseVertex, size_t inFirstInstance) override; void SetViewport(float inX, float inY, float inWidth, float inHeight, float inMinDepth, float inMaxDepth) override; void SetScissor(uint32_t inLeft, uint32_t inTop, uint32_t inRight, uint32_t inBottom) override; void SetPrimitiveTopology(PrimitiveTopology inPrimitiveTopology) override; diff --git a/Engine/Source/RHI-Vulkan/Include/RHI/Vulkan/Common.h b/Engine/Source/RHI-Vulkan/Include/RHI/Vulkan/Common.h index d11a605c0..4c6254842 100644 --- a/Engine/Source/RHI-Vulkan/Include/RHI/Vulkan/Common.h +++ b/Engine/Source/RHI-Vulkan/Include/RHI/Vulkan/Common.h @@ -18,7 +18,7 @@ namespace RHI::Vulkan { ECIMPL_ITEM(VK_PHYSICAL_DEVICE_TYPE_OTHER, GpuType::software) ECIMPL_ITEM(VK_PHYSICAL_DEVICE_TYPE_INTEGRATED_GPU, GpuType::hardware) ECIMPL_ITEM(VK_PHYSICAL_DEVICE_TYPE_DISCRETE_GPU, GpuType::hardware) - ECIMPL_ITEM(VK_PHYSICAL_DEVICE_TYPE_VIRTUAL_GPU, GpuType::software) + ECIMPL_ITEM(VK_PHYSICAL_DEVICE_TYPE_VIRTUAL_GPU, GpuType::hardware) ECIMPL_ITEM(VK_PHYSICAL_DEVICE_TYPE_CPU, GpuType::software) ECIMPL_END(GpuType) @@ -110,6 +110,7 @@ namespace RHI::Vulkan { ECIMPL_ITEM(PrimitiveTopologyType::point, VK_PRIMITIVE_TOPOLOGY_POINT_LIST) ECIMPL_ITEM(PrimitiveTopologyType::line, VK_PRIMITIVE_TOPOLOGY_LINE_LIST) ECIMPL_ITEM(PrimitiveTopologyType::triangle, VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST) + ECIMPL_ITEM(PrimitiveTopologyType::patch, VK_PRIMITIVE_TOPOLOGY_PATCH_LIST) ECIMPL_END(VkPrimitiveTopology) ECIMPL_BEGIN(FillMode, VkPolygonMode) @@ -127,6 +128,7 @@ namespace RHI::Vulkan { ECIMPL_ITEM(PrimitiveTopology::lineStripAdj, VK_PRIMITIVE_TOPOLOGY_LINE_STRIP_WITH_ADJACENCY) ECIMPL_ITEM(PrimitiveTopology::triangleListAdj, VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST_WITH_ADJACENCY) ECIMPL_ITEM(PrimitiveTopology::triangleStripAdj, VK_PRIMITIVE_TOPOLOGY_TRIANGLE_STRIP_WITH_ADJACENCY) + ECIMPL_ITEM(PrimitiveTopology::patchList, VK_PRIMITIVE_TOPOLOGY_PATCH_LIST) ECIMPL_END(VkPrimitiveTopology) ECIMPL_BEGIN(CullMode, VkCullModeFlagBits) diff --git a/Engine/Source/RHI-Vulkan/Include/RHI/Vulkan/Device.h b/Engine/Source/RHI-Vulkan/Include/RHI/Vulkan/Device.h index 951705ff2..2c8ff528b 100644 --- a/Engine/Source/RHI-Vulkan/Include/RHI/Vulkan/Device.h +++ b/Engine/Source/RHI-Vulkan/Include/RHI/Vulkan/Device.h @@ -44,7 +44,7 @@ namespace RHI::Vulkan { Common::UniquePtr CreateQuerySet(const QuerySetCreateInfo& inCreateInfo) override; bool CheckSwapChainFormatSupport(Surface* inSurface, PixelFormat inFormat, ColorSpace inColorSpace) override; - TextureSubResourceCopyFootprint GetTextureSubResourceCopyFootprint(const Texture& texture, const TextureSubResourceInfo& subResourceInfo) override; + TextureSubResourceCopyFootprint GetTextureSubResourceCopyFootprint(const Texture& texture, const TextureSubResourceInfo& subResourceInfo, const Common::UVec3& copyRegion) override; VkDevice GetNative() const; VmaAllocator& GetNativeAllocator(); diff --git a/Engine/Source/RHI-Vulkan/Include/RHI/Vulkan/Instance.h b/Engine/Source/RHI-Vulkan/Include/RHI/Vulkan/Instance.h index 3fccb4dac..68c6bdab5 100644 --- a/Engine/Source/RHI-Vulkan/Include/RHI/Vulkan/Instance.h +++ b/Engine/Source/RHI-Vulkan/Include/RHI/Vulkan/Instance.h @@ -23,7 +23,6 @@ namespace RHI::Vulkan { RHIType GetRHIType() override; uint32_t GetGpuNum() override; Gpu* GetGpu(uint32_t inIndex) override; - void Destroy() override; VkInstance GetNative() const; diff --git a/Engine/Source/RHI-Vulkan/Include/RHI/Vulkan/Queue.h b/Engine/Source/RHI-Vulkan/Include/RHI/Vulkan/Queue.h index d2554814d..39e638b2a 100644 --- a/Engine/Source/RHI-Vulkan/Include/RHI/Vulkan/Queue.h +++ b/Engine/Source/RHI-Vulkan/Include/RHI/Vulkan/Queue.h @@ -18,12 +18,13 @@ namespace RHI::Vulkan { class VulkanQueue final : public Queue { public: NonCopyable(VulkanQueue) - VulkanQueue(VulkanDevice& inDevice, QueueType inType, VkQueue inNativeQueue, std::shared_ptr inNativeQueueMutex); + VulkanQueue(VulkanDevice& inDevice, QueueType inType, uint32_t inFamilyIndex, VkQueue inNativeQueue, std::shared_ptr inNativeQueueMutex); ~VulkanQueue() override; void Flush(Fence* inFenceToSignal) override; float GetTimestampPeriod() override; + uint32_t GetFamilyIndex() const; VkQueue GetNative() const; VkResult Present(const VkPresentInfoKHR& inPresentInfo) const; void WaitIdle() const; @@ -32,6 +33,7 @@ namespace RHI::Vulkan { void SubmitInternal(CommandBuffer* inCmdBuffer, const QueueSubmitInfo& inSubmitInfo) override; VulkanDevice& device; + uint32_t familyIndex; VkQueue nativeQueue; std::shared_ptr nativeQueueMutex; }; diff --git a/Engine/Source/RHI-Vulkan/Src/CommandRecorder.cpp b/Engine/Source/RHI-Vulkan/Src/CommandRecorder.cpp index 3e57c04cf..9294c8d58 100644 --- a/Engine/Source/RHI-Vulkan/Src/CommandRecorder.cpp +++ b/Engine/Source/RHI-Vulkan/Src/CommandRecorder.cpp @@ -152,19 +152,22 @@ namespace RHI::Vulkan { return result; } - static VkBufferImageCopy GetNativeBufferImageCopy(Device& device, const Texture& texture, const BufferTextureCopyInfo& copyInfo) + static VkBufferImageCopy GetNativeBufferImageCopy(const Texture& texture, const BufferTextureCopyInfo& copyInfo, const TextureAspect aspect, const size_t aspectIndex) { - const auto footprint = device.GetTextureSubResourceCopyFootprint(texture, copyInfo.textureSubResource); // NOLINT + Assert(aspect != TextureAspect::depthStencil); + const auto bytesPerPixel = GetTextureAspectBytesPerPixel(texture.GetCreateInfo().format, aspect); + const auto planeBytes = copyInfo.bufferSlicePitch * copyInfo.copyRegion.z; + if (aspect == TextureAspect::depth || aspect == TextureAspect::stencil) { + Assert((copyInfo.bufferOffset + planeBytes * aspectIndex) % 4 == 0); + } VkBufferImageCopy result {}; - result.bufferOffset = copyInfo.bufferOffset; - // bufferRowLength/bufferImageHeight are measured in texels and describe how the linear buffer data is strided; - // they mirror the full sub-resource footprint, while imageExtent selects the copied window within it. - result.bufferRowLength = static_cast(footprint.rowPitch / footprint.bytesPerPixel); - result.bufferImageHeight = footprint.extent.y; + result.bufferOffset = copyInfo.bufferOffset + planeBytes * aspectIndex; + result.bufferRowLength = static_cast(copyInfo.bufferRowPitch / bytesPerPixel); + result.bufferImageHeight = static_cast(copyInfo.bufferSlicePitch / copyInfo.bufferRowPitch); result.imageOffset = { static_cast(copyInfo.textureOrigin.x), static_cast(copyInfo.textureOrigin.y), static_cast(copyInfo.textureOrigin.z) }; result.imageExtent = { copyInfo.copyRegion.x, copyInfo.copyRegion.y, copyInfo.copyRegion.z }; - result.imageSubresource = GetNativeImageSubResourceLayers(copyInfo.textureSubResource); + result.imageSubresource = GetNativeImageSubResourceLayers(TextureSubResourceInfo(copyInfo.textureSubResource.mipLevel, copyInfo.textureSubResource.arrayLayer, aspect)); return result; } } @@ -332,8 +335,12 @@ namespace RHI::Vulkan { const auto* srcBuffer = static_cast(src); const auto* dstTexture = static_cast(dst); - const VkBufferImageCopy nativeBufferImageCopy = GetNativeBufferImageCopy(device, *dst, copyInfo); - vkCmdCopyBufferToImage(commandBuffer.GetNative(), srcBuffer->GetNative(), dstTexture->GetNative(), VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, &nativeBufferImageCopy); + RHI::Internal::ValidateBufferTextureCopy(*src, *dst, copyInfo); + const auto aspects = GetTextureAspectComponents(copyInfo.textureSubResource.aspect); + for (size_t aspectIndex = 0; aspectIndex < aspects.size(); aspectIndex++) { + const VkBufferImageCopy nativeBufferImageCopy = GetNativeBufferImageCopy(*dst, copyInfo, aspects[aspectIndex], aspectIndex); + vkCmdCopyBufferToImage(commandBuffer.GetNative(), srcBuffer->GetNative(), dstTexture->GetNative(), VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, &nativeBufferImageCopy); + } } void VulkanCopyPassCommandRecorder::CopyTextureToBuffer(Texture* src, Buffer* dst, const BufferTextureCopyInfo& copyInfo) @@ -341,8 +348,12 @@ namespace RHI::Vulkan { const auto* srcTexture = static_cast(src); const auto* dstBuffer = static_cast(dst); - const VkBufferImageCopy nativeBufferImageCopy = GetNativeBufferImageCopy(device, *src, copyInfo); - vkCmdCopyImageToBuffer(commandBuffer.GetNative(), srcTexture->GetNative(), VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, dstBuffer->GetNative(), 1, &nativeBufferImageCopy); + RHI::Internal::ValidateBufferTextureCopy(*dst, *src, copyInfo); + const auto aspects = GetTextureAspectComponents(copyInfo.textureSubResource.aspect); + for (size_t aspectIndex = 0; aspectIndex < aspects.size(); aspectIndex++) { + const VkBufferImageCopy nativeBufferImageCopy = GetNativeBufferImageCopy(*src, copyInfo, aspects[aspectIndex], aspectIndex); + vkCmdCopyImageToBuffer(commandBuffer.GetNative(), srcTexture->GetNative(), VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, dstBuffer->GetNative(), 1, &nativeBufferImageCopy); + } } void VulkanCopyPassCommandRecorder::CopyTextureToTexture(Texture* src, Texture* dst, const TextureCopyInfo& copyInfo) @@ -350,14 +361,20 @@ namespace RHI::Vulkan { const auto* srcTexture = static_cast(src); const auto* dstTexture = static_cast(dst); - VkImageCopy nativeImageCopy {}; - nativeImageCopy.srcSubresource = GetNativeImageSubResourceLayers(copyInfo.srcSubResource); - nativeImageCopy.srcOffset = { static_cast(copyInfo.srcOrigin.x), static_cast(copyInfo.srcOrigin.y), static_cast(copyInfo.srcOrigin.z) }; - nativeImageCopy.dstSubresource = GetNativeImageSubResourceLayers(copyInfo.dstSubResource); - nativeImageCopy.dstOffset = { static_cast(copyInfo.dstOrigin.x), static_cast(copyInfo.dstOrigin.y), static_cast(copyInfo.dstOrigin.z) }; - nativeImageCopy.extent = { copyInfo.copyRegion.x, copyInfo.copyRegion.y, copyInfo.copyRegion.z }; - - vkCmdCopyImage(commandBuffer.GetNative(), srcTexture->GetNative(), VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, dstTexture->GetNative(), VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, &nativeImageCopy); + const auto srcAspects = GetTextureAspectComponents(copyInfo.srcSubResource.aspect); + const auto dstAspects = GetTextureAspectComponents(copyInfo.dstSubResource.aspect); + Assert(srcAspects.size() == dstAspects.size()); + for (size_t aspectIndex = 0; aspectIndex < srcAspects.size(); aspectIndex++) { + Assert(srcAspects[aspectIndex] == dstAspects[aspectIndex]); + VkImageCopy nativeImageCopy {}; + nativeImageCopy.srcSubresource = GetNativeImageSubResourceLayers(TextureSubResourceInfo(copyInfo.srcSubResource.mipLevel, copyInfo.srcSubResource.arrayLayer, srcAspects[aspectIndex])); + nativeImageCopy.srcOffset = { static_cast(copyInfo.srcOrigin.x), static_cast(copyInfo.srcOrigin.y), static_cast(copyInfo.srcOrigin.z) }; + nativeImageCopy.dstSubresource = GetNativeImageSubResourceLayers(TextureSubResourceInfo(copyInfo.dstSubResource.mipLevel, copyInfo.dstSubResource.arrayLayer, dstAspects[aspectIndex])); + nativeImageCopy.dstOffset = { static_cast(copyInfo.dstOrigin.x), static_cast(copyInfo.dstOrigin.y), static_cast(copyInfo.dstOrigin.z) }; + nativeImageCopy.extent = { copyInfo.copyRegion.x, copyInfo.copyRegion.y, copyInfo.copyRegion.z }; + + vkCmdCopyImage(commandBuffer.GetNative(), srcTexture->GetNative(), VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, dstTexture->GetNative(), VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, &nativeImageCopy); + } } void VulkanCopyPassCommandRecorder::EndPass() @@ -584,7 +601,7 @@ namespace RHI::Vulkan { vkCmdDraw(commandBuffer.GetNative(), inVertexCount, inInstanceCount, inFirstVertex, inFirstInstance); } - void VulkanRasterPassCommandRecorder::DrawIndexed(const size_t inIndexCount, const size_t inInstanceCount, const size_t inFirstIndex, const size_t inBaseVertex, const size_t inFirstInstance) + void VulkanRasterPassCommandRecorder::DrawIndexed(const size_t inIndexCount, const size_t inInstanceCount, const size_t inFirstIndex, const int32_t inBaseVertex, const size_t inFirstInstance) { vkCmdDrawIndexed(commandBuffer.GetNative(), inIndexCount, inInstanceCount, inFirstIndex, inBaseVertex, inFirstInstance); } diff --git a/Engine/Source/RHI-Vulkan/Src/Device.cpp b/Engine/Source/RHI-Vulkan/Src/Device.cpp index 0d73c3989..e8fcab095 100644 --- a/Engine/Source/RHI-Vulkan/Src/Device.cpp +++ b/Engine/Source/RHI-Vulkan/Src/Device.cpp @@ -36,7 +36,6 @@ namespace RHI::Vulkan { "VK_KHR_depth_stencil_resolve", "VK_KHR_create_renderpass2", #if PLATFORM_MACOS - "VK_KHR_portability_subset", "VK_EXT_extended_dynamic_state" #endif }; @@ -232,22 +231,30 @@ namespace RHI::Vulkan { return iter != surfaceFormats.end(); } - TextureSubResourceCopyFootprint VulkanDevice::GetTextureSubResourceCopyFootprint(const Texture& texture, const TextureSubResourceInfo& subResourceInfo) + TextureSubResourceCopyFootprint VulkanDevice::GetTextureSubResourceCopyFootprint(const Texture& texture, const TextureSubResourceInfo& subResourceInfo, const Common::UVec3& copyRegion) { const auto& createInfo = texture.GetCreateInfo(); const auto mipLevel = subResourceInfo.mipLevel; const auto baseDepth = createInfo.type == TextureType::t3D ? createInfo.depthOrArraySize : 1; - - TextureSubResourceCopyFootprint result {}; - result.extent = { + const Common::UVec3 subResourceExtent = { std::max(createInfo.width >> mipLevel, 1u), std::max(createInfo.height >> mipLevel, 1u), std::max(baseDepth >> mipLevel, 1u) }; - result.bytesPerPixel = GetBytesPerPixel(createInfo.format); + const auto useFullSubResource = copyRegion == Common::UVec3Consts::zero; + const auto extent = useFullSubResource ? subResourceExtent : copyRegion; + Assert(extent.x <= subResourceExtent.x && extent.y <= subResourceExtent.y && extent.z <= subResourceExtent.z); + + TextureSubResourceCopyFootprint result {}; + result.extent = extent; + const auto aspects = GetTextureAspectComponents(subResourceInfo.aspect); + result.bytesPerPixel = 0; + for (const auto aspect : aspects) { + result.bytesPerPixel = std::max(result.bytesPerPixel, GetTextureAspectBytesPerPixel(createInfo.format, aspect)); + } result.rowPitch = result.bytesPerPixel * result.extent.x; result.slicePitch = result.rowPitch * result.extent.y; - result.totalBytes = result.slicePitch * result.extent.z; + result.totalBytes = result.slicePitch * result.extent.z * aspects.size(); return result; } @@ -359,6 +366,8 @@ namespace RHI::Vulkan { enabledFeatures.textureCompressionBC = supportedFeatures.features.textureCompressionBC; enabledFeatures.occlusionQueryPrecise = supportedFeatures.features.occlusionQueryPrecise; enabledFeatures.imageCubeArray = supportedFeatures.features.imageCubeArray; + enabledFeatures.geometryShader = supportedFeatures.features.geometryShader; + enabledFeatures.tessellationShader = supportedFeatures.features.tessellationShader; VkDeviceCreateInfo deviceCreateInfo = {}; deviceCreateInfo.sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO; @@ -376,8 +385,21 @@ namespace RHI::Vulkan { extendedDynamicStateFeatures.extendedDynamicState = VK_TRUE; dynamicRenderingFeatures.pNext = &extendedDynamicStateFeatures; - deviceCreateInfo.ppEnabledExtensionNames = requiredExtensions.data(); - deviceCreateInfo.enabledExtensionCount = static_cast(requiredExtensions.size()); + std::vector enabledExtensions = requiredExtensions; +#if PLATFORM_MACOS + constexpr std::string_view portabilitySubsetExtensionName = "VK_KHR_portability_subset"; + uint32_t extensionCount = 0; + vkEnumerateDeviceExtensionProperties(gpu.GetNative(), nullptr, &extensionCount, nullptr); + std::vector extensions(extensionCount); + vkEnumerateDeviceExtensionProperties(gpu.GetNative(), nullptr, &extensionCount, extensions.data()); + if (std::ranges::find_if(extensions, [portabilitySubsetExtensionName](const VkExtensionProperties& inExtension) -> bool { + return std::string_view(inExtension.extensionName) == portabilitySubsetExtensionName; + }) != extensions.end()) { + enabledExtensions.emplace_back(portabilitySubsetExtensionName.data()); + } +#endif + deviceCreateInfo.ppEnabledExtensionNames = enabledExtensions.data(); + deviceCreateInfo.enabledExtensionCount = static_cast(enabledExtensions.size()); Assert(vkCreateDevice(gpu.GetNative(), &deviceCreateInfo, nullptr, &nativeDevice) == VK_SUCCESS); } @@ -404,7 +426,7 @@ namespace RHI::Vulkan { if (queueMutex == nullptr) { queueMutex = std::make_shared(); } - tempQueues[i] = Common::MakeUnique(*this, queueType, queue, queueMutex); + tempQueues[i] = Common::MakeUnique(*this, queueType, queueFamilyIndex, queue, queueMutex); } queues[queueType] = std::move(tempQueues); diff --git a/Engine/Source/RHI-Vulkan/Src/Gpu.cpp b/Engine/Source/RHI-Vulkan/Src/Gpu.cpp index 482312ad7..167988939 100644 --- a/Engine/Source/RHI-Vulkan/Src/Gpu.cpp +++ b/Engine/Source/RHI-Vulkan/Src/Gpu.cpp @@ -23,9 +23,22 @@ namespace RHI::Vulkan { vkGetPhysicalDeviceProperties(nativePhysicalDevice, &vkPhysicalDeviceProperties); GpuProperty property {}; + property.name = vkPhysicalDeviceProperties.deviceName; property.vendorId = vkPhysicalDeviceProperties.vendorID; property.deviceId = vkPhysicalDeviceProperties.deviceID; + property.driverVersion = vkPhysicalDeviceProperties.driverVersion; + property.apiVersion = vkPhysicalDeviceProperties.apiVersion; property.type = EnumCast(vkPhysicalDeviceProperties.deviceType); + + VkPhysicalDeviceMemoryProperties memoryProperties; + vkGetPhysicalDeviceMemoryProperties(nativePhysicalDevice, &memoryProperties); + for (uint32_t i = 0; i < memoryProperties.memoryHeapCount; i++) { + if ((memoryProperties.memoryHeaps[i].flags & VK_MEMORY_HEAP_DEVICE_LOCAL_BIT) != 0) { + property.dedicatedVideoMemorySize += memoryProperties.memoryHeaps[i].size; + } else { + property.sharedSystemMemorySize += memoryProperties.memoryHeaps[i].size; + } + } return property; } @@ -44,6 +57,8 @@ namespace RHI::Vulkan { if (features.multiDrawIndirect) { result = result | FeatureBits::multiDrawIndirect; } if (features.drawIndirectFirstInstance) { result = result | FeatureBits::drawIndirectFirstInstance; } if (features.imageCubeArray) { result = result | FeatureBits::textureCubeArray; } + if (features.geometryShader) { result = result | FeatureBits::geometryShader; } + if (features.tessellationShader) { result = result | FeatureBits::tessellationShader; } return result; } diff --git a/Engine/Source/RHI-Vulkan/Src/Instance.cpp b/Engine/Source/RHI-Vulkan/Src/Instance.cpp index 354a5c2b7..b5b3f4d67 100644 --- a/Engine/Source/RHI-Vulkan/Src/Instance.cpp +++ b/Engine/Source/RHI-Vulkan/Src/Instance.cpp @@ -80,8 +80,13 @@ namespace RHI::Vulkan { #endif , nativeInstance(VK_NULL_HANDLE) { + if (GetCreateInfo().useSoftwareGpu) { + Common::PlatformUtils::SetEnvVar("VK_DRIVER_FILES", Internal::GetRuntimeManifestPath("vk_swiftshader_icd.json")); + } #if PLATFORM_MACOS - Common::PlatformUtils::SetEnvVar("VK_DRIVER_FILES", Internal::GetRuntimeManifestPath("MoltenVK_icd.json")); + else { + Common::PlatformUtils::SetEnvVar("VK_DRIVER_FILES", Internal::GetRuntimeManifestPath("MoltenVK_icd.json")); + } #endif #if BUILD_CONFIG_DEBUG @@ -240,8 +245,4 @@ namespace RHI::Vulkan { } #endif - void VulkanInstance::Destroy() - { - delete this; - } } diff --git a/Engine/Source/RHI-Vulkan/Src/Pipeline.cpp b/Engine/Source/RHI-Vulkan/Src/Pipeline.cpp index 14f9620fa..745f68f22 100644 --- a/Engine/Source/RHI-Vulkan/Src/Pipeline.cpp +++ b/Engine/Source/RHI-Vulkan/Src/Pipeline.cpp @@ -46,10 +46,18 @@ namespace RHI::Vulkan { static VkPipelineInputAssemblyStateCreateInfo ConstructInputAssembly(const RasterPipelineCreateInfo& createInfo) { + const auto topologyType = createInfo.primitiveState.topologyType; + const bool stripTopology = topologyType == PrimitiveTopologyType::line || topologyType == PrimitiveTopologyType::triangle; + const bool primitiveRestartEnabled = stripTopology && createInfo.primitiveState.stripIndexFormat != IndexFormat::max; + VkPipelineInputAssemblyStateCreateInfo assemblyInfo = {}; assemblyInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO; - assemblyInfo.topology = EnumCast(createInfo.primitiveState.topologyType); - assemblyInfo.primitiveRestartEnable = VK_FALSE; + assemblyInfo.topology = EnumCast(topologyType); + assemblyInfo.primitiveRestartEnable = primitiveRestartEnabled ? VK_TRUE : VK_FALSE; + if (primitiveRestartEnabled) { + // Vulkan validates primitive restart against this value even when the topology is dynamic. + assemblyInfo.topology = topologyType == PrimitiveTopologyType::line ? VK_PRIMITIVE_TOPOLOGY_LINE_STRIP : VK_PRIMITIVE_TOPOLOGY_TRIANGLE_STRIP; + } return assemblyInfo; } @@ -203,8 +211,14 @@ namespace RHI::Vulkan { stages.emplace_back(stageInfo); }; setStage(inCreateInfo.vertexShader, VK_SHADER_STAGE_VERTEX_BIT); + setStage(inCreateInfo.hullShader, VK_SHADER_STAGE_TESSELLATION_CONTROL_BIT); + setStage(inCreateInfo.domainShader, VK_SHADER_STAGE_TESSELLATION_EVALUATION_BIT); + setStage(inCreateInfo.geometryShader, VK_SHADER_STAGE_GEOMETRY_BIT); setStage(inCreateInfo.pixelShader, VK_SHADER_STAGE_FRAGMENT_BIT); + AssertWithReason(inCreateInfo.geometryShader == nullptr || device.GetEnabledFeatures().geometryShader == VK_TRUE, "Vulkan geometry shader feature is not supported"); + AssertWithReason(inCreateInfo.hullShader == nullptr || device.GetEnabledFeatures().tessellationShader == VK_TRUE, "Vulkan tessellation shader feature is not supported"); + const std::array dynamicStates = { VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR, @@ -220,6 +234,9 @@ namespace RHI::Vulkan { VkPipelineMultisampleStateCreateInfo multiSampleInfo = ConstructMultiSampleState(inCreateInfo); VkPipelineDepthStencilStateCreateInfo dsInfo = ConstructDepthStencil(inCreateInfo); VkPipelineInputAssemblyStateCreateInfo assemblyInfo = ConstructInputAssembly(inCreateInfo); + VkPipelineTessellationStateCreateInfo tessellationInfo = {}; + tessellationInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_TESSELLATION_STATE_CREATE_INFO; + tessellationInfo.patchControlPoints = inCreateInfo.primitiveState.patchControlPoints; VkPipelineRasterizationStateCreateInfo rasterState = ConstructRasterization(device, inCreateInfo); VkPipelineViewportStateCreateInfo viewportState = ConstructViewportInfo(inCreateInfo); @@ -257,7 +274,7 @@ namespace RHI::Vulkan { pipelineCreateInfo.pInputAssemblyState = &assemblyInfo; pipelineCreateInfo.pRasterizationState = &rasterState; pipelineCreateInfo.pViewportState = &viewportState; - pipelineCreateInfo.pTessellationState = nullptr; + pipelineCreateInfo.pTessellationState = inCreateInfo.hullShader != nullptr ? &tessellationInfo : nullptr; pipelineCreateInfo.pColorBlendState = &colorInfo; pipelineCreateInfo.pVertexInputState = &vtxInput; pipelineCreateInfo.pNext = &pipelineRenderingCreateInfo; diff --git a/Engine/Source/RHI-Vulkan/Src/Queue.cpp b/Engine/Source/RHI-Vulkan/Src/Queue.cpp index 77c783b9f..8f747bd66 100644 --- a/Engine/Source/RHI-Vulkan/Src/Queue.cpp +++ b/Engine/Source/RHI-Vulkan/Src/Queue.cpp @@ -12,9 +12,10 @@ #include namespace RHI::Vulkan { - VulkanQueue::VulkanQueue(VulkanDevice& inDevice, const QueueType inType, const VkQueue inNativeQueue, std::shared_ptr inNativeQueueMutex) + VulkanQueue::VulkanQueue(VulkanDevice& inDevice, const QueueType inType, const uint32_t inFamilyIndex, const VkQueue inNativeQueue, std::shared_ptr inNativeQueueMutex) : Queue(inType) , device(inDevice) + , familyIndex(inFamilyIndex) , nativeQueue(inNativeQueue) , nativeQueueMutex(std::move(inNativeQueueMutex)) { @@ -83,6 +84,11 @@ namespace RHI::Vulkan { return properties.limits.timestampPeriod; } + uint32_t VulkanQueue::GetFamilyIndex() const + { + return familyIndex; + } + VkQueue VulkanQueue::GetNative() const { return nativeQueue; diff --git a/Engine/Source/RHI-Vulkan/Src/ShaderModule.cpp b/Engine/Source/RHI-Vulkan/Src/ShaderModule.cpp index 6b3d53bc1..6c6a68797 100644 --- a/Engine/Source/RHI-Vulkan/Src/ShaderModule.cpp +++ b/Engine/Source/RHI-Vulkan/Src/ShaderModule.cpp @@ -37,11 +37,13 @@ namespace RHI::Vulkan { void VulkanShaderModule::CreateNativeShaderModule(const ShaderModuleCreateInfo& createInfo) { + Assert(createInfo.byteCode != nullptr); + VkShaderModuleCreateInfo moduleCreateInfo = {}; moduleCreateInfo.sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO; - moduleCreateInfo.codeSize = createInfo.size; - moduleCreateInfo.pCode = static_cast(createInfo.byteCode); + moduleCreateInfo.codeSize = createInfo.byteCode->GetSize(); + moduleCreateInfo.pCode = static_cast(createInfo.byteCode->GetData()); Assert(vkCreateShaderModule(device.GetNative(), &moduleCreateInfo, nullptr, &nativeShaderModule) == VK_SUCCESS); } -} \ No newline at end of file +} diff --git a/Engine/Source/RHI-Vulkan/Src/SwapChain.cpp b/Engine/Source/RHI-Vulkan/Src/SwapChain.cpp index e0d69209c..f3edb30ba 100644 --- a/Engine/Source/RHI-Vulkan/Src/SwapChain.cpp +++ b/Engine/Source/RHI-Vulkan/Src/SwapChain.cpp @@ -82,6 +82,10 @@ namespace RHI::Vulkan { Assert(vkSurface); const auto surface = vkSurface->GetNative(); + VkBool32 presentSupported = VK_FALSE; + Assert(vkGetPhysicalDeviceSurfaceSupportKHR(device.GetGpu().GetNative(), queue.GetFamilyIndex(), surface, &presentSupported) == VK_SUCCESS); + AssertWithReason(presentSupported == VK_TRUE, "the requested vulkan present queue does not support the swap chain surface"); + VkSurfaceCapabilitiesKHR surfaceCap; vkGetPhysicalDeviceSurfaceCapabilitiesKHR(device.GetGpu().GetNative(), surface, &surfaceCap); diff --git a/Engine/Source/RHI-Vulkan/Src/VulkanRHIModule.cpp b/Engine/Source/RHI-Vulkan/Src/VulkanRHIModule.cpp index f3b47fddc..02cf9da4a 100644 --- a/Engine/Source/RHI-Vulkan/Src/VulkanRHIModule.cpp +++ b/Engine/Source/RHI-Vulkan/Src/VulkanRHIModule.cpp @@ -17,7 +17,9 @@ namespace RHI::Vulkan { void VulkanRHIModule::OnUnload() { - delete gInstance; + auto* instance = gInstance; + gInstance = nullptr; + delete instance; } Core::ModuleType VulkanRHIModule::Type() const diff --git a/Engine/Source/RHI/Include/RHI/CommandRecorder.h b/Engine/Source/RHI/Include/RHI/CommandRecorder.h index 160925e43..d8be9152c 100644 --- a/Engine/Source/RHI/Include/RHI/CommandRecorder.h +++ b/Engine/Source/RHI/Include/RHI/CommandRecorder.h @@ -38,6 +38,8 @@ namespace RHI { struct TextureSubResourceCopyFootprint { Common::UVec3 extent; + // For depthStencil this is the larger per-plane element size. The layout contains depth followed by stencil, + // with both planes using rowPitch and slicePitch; totalBytes covers all planes. size_t bytesPerPixel; size_t rowPitch; size_t slicePitch; @@ -78,22 +80,26 @@ namespace RHI { struct BufferTextureCopyInfo { size_t bufferOffset; + size_t bufferRowPitch; + size_t bufferSlicePitch; TextureSubResourceInfo textureSubResource; Common::UVec3 textureOrigin; Common::UVec3 copyRegion; - explicit BufferTextureCopyInfo( - size_t inBufferOffset = 0, - const TextureSubResourceInfo& inTextureSubResource = TextureSubResourceInfo(), - const Common::UVec3& inTextureOrigin = Common::UVec3Consts::zero, - const Common::UVec3& inCopyRegion = Common::UVec3Consts::zero); + explicit BufferTextureCopyInfo(size_t inBufferOffset = 0, const TextureSubResourceInfo& inTextureSubResource = TextureSubResourceInfo(), const Common::UVec3& inTextureOrigin = Common::UVec3Consts::zero, const Common::UVec3& inCopyRegion = Common::UVec3Consts::zero, size_t inBufferRowPitch = 0, size_t inBufferSlicePitch = 0); BufferTextureCopyInfo& SetBufferOffset(size_t inBufferOffset); + BufferTextureCopyInfo& SetBufferRowPitch(size_t inBufferRowPitch); + BufferTextureCopyInfo& SetBufferSlicePitch(size_t inBufferSlicePitch); BufferTextureCopyInfo& SetTextureSubResource(const TextureSubResourceInfo& inTextureSubResource); BufferTextureCopyInfo& SetTextureOrigin(const Common::UVec3& inTextureOrigin); BufferTextureCopyInfo& SetCopyRegion(const Common::UVec3& inCopyRegion); }; + namespace Internal { + void ValidateBufferTextureCopy(const Buffer& buffer, const Texture& texture, const BufferTextureCopyInfo& copyInfo); + } + struct DrawIndirectArguments { uint32_t vertexCount = 0; uint32_t instanceCount = 0; @@ -217,7 +223,9 @@ namespace RHI { ~CopyPassCommandRecorder() override; virtual void CopyBufferToBuffer(Buffer* src, Buffer* dst, const BufferCopyInfo& copyInfo) = 0; - // NOTICE: CopyBufferToTexture/CopyTextureToBuffer treat buffer contains copy region (sub-image) data from offset + // The buffer layout starts at bufferOffset and is described explicitly by bufferRowPitch and bufferSlicePitch. + // A combined depth-stencil copy stores the depth plane first and the stencil plane second. Each plane occupies + // bufferSlicePitch * copyRegion.z bytes and uses the same row and slice pitches. virtual void CopyBufferToTexture(Buffer* src, Texture* dst, const BufferTextureCopyInfo& copyInfo) = 0; virtual void CopyTextureToBuffer(Texture* src, Buffer* dst, const BufferTextureCopyInfo& copyInfo) = 0; virtual void CopyTextureToTexture(Texture* src, Texture* dst, const TextureCopyInfo& copyInfo) = 0; @@ -254,7 +262,7 @@ namespace RHI { virtual void SetIndexBuffer(BufferView* bufferView) = 0; virtual void SetVertexBuffer(size_t slot, BufferView* bufferView) = 0; virtual void Draw(size_t vertexCount, size_t instanceCount, size_t firstVertex, size_t firstInstance) = 0; - virtual void DrawIndexed(size_t indexCount, size_t instanceCount, size_t firstIndex, size_t baseVertex, size_t firstInstance) = 0; + virtual void DrawIndexed(size_t indexCount, size_t instanceCount, size_t firstIndex, int32_t baseVertex, size_t firstInstance) = 0; virtual void SetViewport(float topLeftX, float topLeftY, float width, float height, float minDepth, float maxDepth) = 0; virtual void SetScissor(uint32_t left, uint32_t top, uint32_t right, uint32_t bottom) = 0; virtual void SetPrimitiveTopology(PrimitiveTopology primitiveTopology) = 0; diff --git a/Engine/Source/RHI/Include/RHI/Common.h b/Engine/Source/RHI/Include/RHI/Common.h index c7082188f..e72f2496a 100644 --- a/Engine/Source/RHI/Include/RHI/Common.h +++ b/Engine/Source/RHI/Include/RHI/Common.h @@ -5,6 +5,7 @@ #pragma once #include +#include #include #include @@ -283,6 +284,7 @@ namespace RHI { point, line, triangle, + patch, max }; @@ -296,6 +298,7 @@ namespace RHI { lineStripAdj, triangleListAdj, triangleStripAdj, + patchList, max }; @@ -488,7 +491,9 @@ namespace RHI { multiDrawIndirect = 0x8, drawIndirectFirstInstance = 0x10, textureCubeArray = 0x20, - max = 0x40 + geometryShader = 0x40, + tessellationShader = 0x80, + max = 0x100 }; using FeatureFlags = Common::Flags; DECLARE_FLAG_BITS_OP(FeatureFlags, FeatureBits) @@ -496,6 +501,8 @@ namespace RHI { namespace RHI { size_t GetBytesPerPixel(PixelFormat format); + size_t GetTextureAspectBytesPerPixel(PixelFormat format, TextureAspect aspect); TextureAspect GetTextureAspect(PixelFormat format); + std::span GetTextureAspectComponents(TextureAspect aspect); TextureState GetDepthStencilTextureState(TextureAspect aspect, bool depthReadOnly, bool stencilReadOnly); } diff --git a/Engine/Source/RHI/Include/RHI/Device.h b/Engine/Source/RHI/Include/RHI/Device.h index a04a28c4f..5a31427e5 100644 --- a/Engine/Source/RHI/Include/RHI/Device.h +++ b/Engine/Source/RHI/Include/RHI/Device.h @@ -88,7 +88,7 @@ namespace RHI { virtual Common::UniquePtr CreateQuerySet(const QuerySetCreateInfo& createInfo) = 0; virtual bool CheckSwapChainFormatSupport(Surface* surface, PixelFormat format, ColorSpace colorSpace) = 0; - virtual TextureSubResourceCopyFootprint GetTextureSubResourceCopyFootprint(const Texture& texture, const TextureSubResourceInfo& subResourceInfo) = 0; + virtual TextureSubResourceCopyFootprint GetTextureSubResourceCopyFootprint(const Texture& texture, const TextureSubResourceInfo& subResourceInfo, const Common::UVec3& copyRegion = Common::UVec3Consts::zero) = 0; protected: explicit Device(const DeviceCreateInfo& createInfo); diff --git a/Engine/Source/RHI/Include/RHI/Gpu.h b/Engine/Source/RHI/Include/RHI/Gpu.h index 981e86550..6ce5229aa 100644 --- a/Engine/Source/RHI/Include/RHI/Gpu.h +++ b/Engine/Source/RHI/Include/RHI/Gpu.h @@ -5,6 +5,7 @@ #pragma once #include +#include #include #include @@ -15,8 +16,13 @@ namespace RHI { struct DeviceCreateInfo; struct GpuProperty { + std::string name; uint32_t vendorId; uint32_t deviceId; + uint32_t driverVersion; + uint32_t apiVersion; + uint64_t dedicatedVideoMemorySize; + uint64_t sharedSystemMemorySize; GpuType type; }; diff --git a/Engine/Source/RHI/Include/RHI/Instance.h b/Engine/Source/RHI/Include/RHI/Instance.h index 4cdec4a6b..4f540f90f 100644 --- a/Engine/Source/RHI/Include/RHI/Instance.h +++ b/Engine/Source/RHI/Include/RHI/Instance.h @@ -16,6 +16,8 @@ namespace RHI { struct InstanceCreateInfo { InstanceCreateInfo(); + bool useSoftwareGpu; + #if BUILD_CONFIG_DEBUG bool gpuDebug; #endif @@ -40,7 +42,6 @@ namespace RHI { virtual RHIType GetRHIType() = 0; virtual uint32_t GetGpuNum() = 0; virtual Gpu* GetGpu(uint32_t index) = 0; - virtual void Destroy() = 0; protected: explicit Instance(const InstanceCreateInfo& inCreateInfo); diff --git a/Engine/Source/RHI/Include/RHI/Pipeline.h b/Engine/Source/RHI/Include/RHI/Pipeline.h index d3a6ccbf6..658041b35 100644 --- a/Engine/Source/RHI/Include/RHI/Pipeline.h +++ b/Engine/Source/RHI/Include/RHI/Pipeline.h @@ -93,7 +93,8 @@ namespace RHI { IndexFormat stripIndexFormat; FrontFace frontFace; CullMode cullMode; - bool depthClip = true; + bool depthClip; + uint32_t patchControlPoints; explicit PrimitiveState( PrimitiveTopologyType inTopologyType = PrimitiveTopologyType::triangle, @@ -101,7 +102,8 @@ namespace RHI { IndexFormat inStripIndexFormat = IndexFormat::uint16, FrontFace inFrontFace = FrontFace::ccw, CullMode inCullMode = CullMode::back, - bool inDepthClip = true); + bool inDepthClip = true, + uint32_t inPatchControlPoints = 0); PrimitiveState& SetTopologyType(PrimitiveTopologyType inTopologyType); PrimitiveState& SetFillMode(FillMode inFillMode); @@ -109,6 +111,7 @@ namespace RHI { PrimitiveState& SetFrontFace(FrontFace inFrontFace); PrimitiveState& SetCullMode(CullMode inCullMode); PrimitiveState& SetDepthClip(bool inDepthClip); + PrimitiveState& SetPatchControlPoints(uint32_t inPatchControlPoints); }; struct StencilFaceState { @@ -288,8 +291,13 @@ namespace RHI { NonCopyable(RasterPipeline) ~RasterPipeline() override; + const PrimitiveState& GetPrimitiveState() const; + protected: explicit RasterPipeline(const RasterPipelineCreateInfo& createInfo); + + private: + PrimitiveState primitiveState; }; } diff --git a/Engine/Source/RHI/Include/RHI/ShaderModule.h b/Engine/Source/RHI/Include/RHI/ShaderModule.h index 16a72d86b..f3b76dde1 100644 --- a/Engine/Source/RHI/Include/RHI/ShaderModule.h +++ b/Engine/Source/RHI/Include/RHI/ShaderModule.h @@ -7,19 +7,40 @@ #include #include +#include #include namespace RHI { + class ShaderByteCode final { + public: + NonCopyable(ShaderByteCode) + explicit ShaderByteCode(const void* inData, size_t inSize); + explicit ShaderByteCode(const std::vector& inData); + explicit ShaderByteCode(std::vector&& inData); + ~ShaderByteCode(); + + const void* GetData() const; + size_t GetSize() const; + + private: + std::vector data; + }; + + using ShaderByteCodeRef = Common::SharedPtr; + struct ShaderModuleCreateInfo { std::string entryPoint; - const void* byteCode; - size_t size; + ShaderByteCodeRef byteCode; - explicit ShaderModuleCreateInfo(const std::string& inEntryPoint = "", const void* inByteCode = nullptr, size_t inSize = 0); - explicit ShaderModuleCreateInfo(const std::string& inEntryPoint = "", const std::vector& inByteCode = {}); + explicit ShaderModuleCreateInfo(const std::string& inEntryPoint = "", ShaderByteCodeRef inByteCode = {}); + explicit ShaderModuleCreateInfo(const std::string& inEntryPoint, const void* inByteCode, size_t inSize); + explicit ShaderModuleCreateInfo(const std::string& inEntryPoint, const std::vector& inByteCode); + explicit ShaderModuleCreateInfo(const std::string& inEntryPoint, std::vector&& inByteCode); - ShaderModuleCreateInfo& SetByteCode(const void* inByteCode); - ShaderModuleCreateInfo& SetSize(size_t inSize); + ShaderModuleCreateInfo& SetByteCode(ShaderByteCodeRef inByteCode); + ShaderModuleCreateInfo& SetByteCode(const void* inByteCode, size_t inSize); + ShaderModuleCreateInfo& SetByteCode(const std::vector& inByteCode); + ShaderModuleCreateInfo& SetByteCode(std::vector&& inByteCode); }; class ShaderModule { diff --git a/Engine/Source/RHI/Src/Buffer.cpp b/Engine/Source/RHI/Src/Buffer.cpp index cca632c2e..526f5b944 100644 --- a/Engine/Source/RHI/Src/Buffer.cpp +++ b/Engine/Source/RHI/Src/Buffer.cpp @@ -64,7 +64,12 @@ namespace RHI::Internal { } namespace RHI { - BufferCreateInfo::BufferCreateInfo() = default; + BufferCreateInfo::BufferCreateInfo() + : size(0) + , usages(BufferUsageFlags::null) + , initialState(BufferState::max) + { + } BufferCreateInfo::BufferCreateInfo(const uint32_t inSize, const BufferUsageFlags inUsages, const BufferState inInitialState, std::string inDebugName) : size(inSize) diff --git a/Engine/Source/RHI/Src/CommandRecorder.cpp b/Engine/Source/RHI/Src/CommandRecorder.cpp index c3f67420d..0da4dc05c 100644 --- a/Engine/Source/RHI/Src/CommandRecorder.cpp +++ b/Engine/Source/RHI/Src/CommandRecorder.cpp @@ -2,7 +2,62 @@ // Created by johnk on 21/2/2022. // +#include +#include + #include +#include +#include + +namespace RHI::Internal { + void ValidateBufferTextureCopy(const Buffer& buffer, const Texture& texture, const BufferTextureCopyInfo& copyInfo) + { + const auto& textureCreateInfo = texture.GetCreateInfo(); + Assert(copyInfo.textureSubResource.mipLevel < textureCreateInfo.mipLevels); + + const auto textureAspect = GetTextureAspect(textureCreateInfo.format); + const auto copyAspects = GetTextureAspectComponents(copyInfo.textureSubResource.aspect); + const auto textureAspects = GetTextureAspectComponents(textureAspect); + for (const auto copyAspect : copyAspects) { + Assert(std::ranges::find(textureAspects, copyAspect) != textureAspects.end()); + } + + const auto arraySize = textureCreateInfo.type == TextureType::t3D ? 1u : textureCreateInfo.depthOrArraySize; + Assert(copyInfo.textureSubResource.arrayLayer < arraySize); + + const auto mipLevel = copyInfo.textureSubResource.mipLevel; + const auto baseDepth = textureCreateInfo.type == TextureType::t3D ? textureCreateInfo.depthOrArraySize : 1u; + const Common::UVec3 subResourceExtent = { + std::max(textureCreateInfo.width >> mipLevel, 1u), + std::max(textureCreateInfo.height >> mipLevel, 1u), + std::max(baseDepth >> mipLevel, 1u) + }; + + Assert(copyInfo.copyRegion.x > 0 && copyInfo.copyRegion.y > 0 && copyInfo.copyRegion.z > 0); + Assert(copyInfo.textureOrigin.x <= subResourceExtent.x && copyInfo.copyRegion.x <= subResourceExtent.x - copyInfo.textureOrigin.x); + Assert(copyInfo.textureOrigin.y <= subResourceExtent.y && copyInfo.copyRegion.y <= subResourceExtent.y - copyInfo.textureOrigin.y); + Assert(copyInfo.textureOrigin.z <= subResourceExtent.z && copyInfo.copyRegion.z <= subResourceExtent.z - copyInfo.textureOrigin.z); + + for (const auto copyAspect : copyAspects) { + const auto bytesPerPixel = GetTextureAspectBytesPerPixel(textureCreateInfo.format, copyAspect); + Assert(copyInfo.copyRegion.x <= std::numeric_limits::max() / bytesPerPixel); + const auto packedRowPitch = bytesPerPixel * copyInfo.copyRegion.x; + Assert(copyInfo.bufferRowPitch >= packedRowPitch && copyInfo.bufferRowPitch % bytesPerPixel == 0); + } + Assert(copyInfo.copyRegion.y <= std::numeric_limits::max() / copyInfo.bufferRowPitch); + Assert(copyInfo.bufferSlicePitch >= copyInfo.bufferRowPitch * copyInfo.copyRegion.y); + Assert(copyInfo.bufferSlicePitch % copyInfo.bufferRowPitch == 0); + Assert(copyInfo.bufferRowPitch <= std::numeric_limits::max()); + Assert(copyInfo.bufferSlicePitch / copyInfo.bufferRowPitch <= std::numeric_limits::max()); + + const auto bufferSize = static_cast(buffer.GetCreateInfo().size); + Assert(copyInfo.bufferOffset <= bufferSize); + Assert(copyInfo.bufferSlicePitch <= std::numeric_limits::max() / copyInfo.copyRegion.z); + const auto planeBytes = copyInfo.bufferSlicePitch * copyInfo.copyRegion.z; + Assert(planeBytes <= std::numeric_limits::max() / copyAspects.size()); + Assert(planeBytes * copyAspects.size() <= bufferSize - copyInfo.bufferOffset); + } +} namespace RHI { TextureSubResourceInfo::TextureSubResourceInfo( @@ -97,8 +152,10 @@ namespace RHI { return *this; } - BufferTextureCopyInfo::BufferTextureCopyInfo(const size_t inBufferOffset, const TextureSubResourceInfo& inTextureSubResource, const Common::UVec3& inTextureOrigin, const Common::UVec3& inCopyRegion) + BufferTextureCopyInfo::BufferTextureCopyInfo(const size_t inBufferOffset, const TextureSubResourceInfo& inTextureSubResource, const Common::UVec3& inTextureOrigin, const Common::UVec3& inCopyRegion, const size_t inBufferRowPitch, const size_t inBufferSlicePitch) : bufferOffset(inBufferOffset) + , bufferRowPitch(inBufferRowPitch) + , bufferSlicePitch(inBufferSlicePitch) , textureSubResource(inTextureSubResource) , textureOrigin(inTextureOrigin) , copyRegion(inCopyRegion) @@ -111,6 +168,18 @@ namespace RHI { return *this; } + BufferTextureCopyInfo& BufferTextureCopyInfo::SetBufferRowPitch(const size_t inBufferRowPitch) + { + bufferRowPitch = inBufferRowPitch; + return *this; + } + + BufferTextureCopyInfo& BufferTextureCopyInfo::SetBufferSlicePitch(const size_t inBufferSlicePitch) + { + bufferSlicePitch = inBufferSlicePitch; + return *this; + } + BufferTextureCopyInfo& BufferTextureCopyInfo::SetTextureSubResource(const TextureSubResourceInfo& inTextureSubResource) { textureSubResource = inTextureSubResource; diff --git a/Engine/Source/RHI/Src/Common.cpp b/Engine/Source/RHI/Src/Common.cpp index eac789f14..6a795c255 100644 --- a/Engine/Source/RHI/Src/Common.cpp +++ b/Engine/Source/RHI/Src/Common.cpp @@ -28,6 +28,18 @@ namespace RHI { return Assert(false), 1; } + size_t GetTextureAspectBytesPerPixel(const PixelFormat format, const TextureAspect aspect) + { + const auto textureAspect = GetTextureAspect(format); + if (textureAspect == TextureAspect::depthStencil) { + Assert(aspect == TextureAspect::depth || aspect == TextureAspect::stencil); + return aspect == TextureAspect::depth ? 4 : 1; + } + + Assert(aspect == textureAspect); + return GetBytesPerPixel(format); + } + TextureAspect GetTextureAspect(const PixelFormat format) { switch (format) { @@ -42,6 +54,27 @@ namespace RHI { } } + std::span GetTextureAspectComponents(const TextureAspect aspect) + { + static constexpr TextureAspect color[] = { TextureAspect::color }; + static constexpr TextureAspect depth[] = { TextureAspect::depth }; + static constexpr TextureAspect stencil[] = { TextureAspect::stencil }; + static constexpr TextureAspect depthStencil[] = { TextureAspect::depth, TextureAspect::stencil }; + + switch (aspect) { + case TextureAspect::color: + return color; + case TextureAspect::depth: + return depth; + case TextureAspect::stencil: + return stencil; + case TextureAspect::depthStencil: + return depthStencil; + default: + return Assert(false), std::span(); + } + } + TextureState GetDepthStencilTextureState(const TextureAspect aspect, const bool depthReadOnly, const bool stencilReadOnly) { if (aspect == TextureAspect::depth) { diff --git a/Engine/Source/RHI/Src/Instance.cpp b/Engine/Source/RHI/Src/Instance.cpp index 4f4a73656..536236d5e 100644 --- a/Engine/Source/RHI/Src/Instance.cpp +++ b/Engine/Source/RHI/Src/Instance.cpp @@ -7,8 +7,9 @@ namespace RHI { InstanceCreateInfo::InstanceCreateInfo() + : useSoftwareGpu(false) #if BUILD_CONFIG_DEBUG - : gpuDebug(false) + , gpuDebug(false) #endif { } @@ -76,6 +77,7 @@ namespace RHI { if (instance == nullptr) { instance = module->CreateRHIInstance(inCreateInfo); } else { + Assert(instance->GetCreateInfo().useSoftwareGpu == inCreateInfo.useSoftwareGpu); #if BUILD_CONFIG_DEBUG Assert(instance->GetCreateInfo().gpuDebug == inCreateInfo.gpuDebug); #endif diff --git a/Engine/Source/RHI/Src/Pipeline.cpp b/Engine/Source/RHI/Src/Pipeline.cpp index 71890f7f2..46d6c1951 100644 --- a/Engine/Source/RHI/Src/Pipeline.cpp +++ b/Engine/Source/RHI/Src/Pipeline.cpp @@ -65,13 +65,15 @@ namespace RHI { const IndexFormat inStripIndexFormat, const FrontFace inFrontFace, const CullMode inCullMode, - const bool inDepthClip) + const bool inDepthClip, + const uint32_t inPatchControlPoints) : topologyType(inTopologyType) , fillMode(inFillMode) , stripIndexFormat(inStripIndexFormat) , frontFace(inFrontFace) , cullMode(inCullMode) , depthClip(inDepthClip) + , patchControlPoints(inPatchControlPoints) { } @@ -111,6 +113,12 @@ namespace RHI { return *this; } + PrimitiveState& PrimitiveState::SetPatchControlPoints(const uint32_t inPatchControlPoints) + { + patchControlPoints = inPatchControlPoints; + return *this; + } + StencilFaceState::StencilFaceState( const CompareFunc inCompareFunc, const StencilOp inFailOp, @@ -433,7 +441,22 @@ namespace RHI { ComputePipeline::~ComputePipeline() = default; - RasterPipeline::RasterPipeline(const RasterPipelineCreateInfo&) {} + RasterPipeline::RasterPipeline(const RasterPipelineCreateInfo& createInfo) + : primitiveState(createInfo.primitiveState) + { + const bool hasHullShader = createInfo.hullShader != nullptr; + const bool hasDomainShader = createInfo.domainShader != nullptr; + const bool hasTessellationShaders = hasHullShader && hasDomainShader; + AssertWithReason(hasHullShader == hasDomainShader, "hull and domain shaders must be provided together"); + AssertWithReason(hasTessellationShaders == (primitiveState.topologyType == PrimitiveTopologyType::patch), "patch topology requires hull and domain shaders"); + AssertWithReason(!hasTessellationShaders || (primitiveState.patchControlPoints >= 1 && primitiveState.patchControlPoints <= 32), "tessellation patch control point count must be between 1 and 32"); + AssertWithReason(hasTessellationShaders || primitiveState.patchControlPoints == 0, "patch control points require hull and domain shaders"); + } + + const PrimitiveState& RasterPipeline::GetPrimitiveState() const + { + return primitiveState; + } RasterPipeline::~RasterPipeline() = default; } diff --git a/Engine/Source/RHI/Src/ShaderModule.cpp b/Engine/Source/RHI/Src/ShaderModule.cpp index c37eb97ad..09910c3aa 100644 --- a/Engine/Source/RHI/Src/ShaderModule.cpp +++ b/Engine/Source/RHI/Src/ShaderModule.cpp @@ -2,32 +2,88 @@ // Created by johnk on 19/2/2022. // +#include +#include + #include namespace RHI { + ShaderByteCode::ShaderByteCode(const void* inData, const size_t inSize) + : data(inSize) + { + Assert(inData != nullptr || inSize == 0); + if (inSize > 0) { + std::memcpy(data.data(), inData, inSize); + } + } + + ShaderByteCode::ShaderByteCode(const std::vector& inData) + : data(inData) + { + } + + ShaderByteCode::ShaderByteCode(std::vector&& inData) + : data(std::move(inData)) + { + } + + ShaderByteCode::~ShaderByteCode() = default; + + const void* ShaderByteCode::GetData() const + { + return data.data(); + } + + size_t ShaderByteCode::GetSize() const + { + return data.size(); + } + + ShaderModuleCreateInfo::ShaderModuleCreateInfo(const std::string& inEntryPoint, ShaderByteCodeRef inByteCode) + : entryPoint(inEntryPoint) + , byteCode(std::move(inByteCode)) + { + } + ShaderModuleCreateInfo::ShaderModuleCreateInfo(const std::string& inEntryPoint, const void* inByteCode, const size_t inSize) : entryPoint(inEntryPoint) - , byteCode(inByteCode) - , size(inSize) + , byteCode(Common::MakeShared(inByteCode, inSize)) { } ShaderModuleCreateInfo::ShaderModuleCreateInfo(const std::string& inEntryPoint, const std::vector& inByteCode) : entryPoint(inEntryPoint) - , byteCode(inByteCode.data()) - , size(inByteCode.size()) + , byteCode(Common::MakeShared(inByteCode)) + { + } + + ShaderModuleCreateInfo::ShaderModuleCreateInfo(const std::string& inEntryPoint, std::vector&& inByteCode) + : entryPoint(inEntryPoint) + , byteCode(Common::MakeShared(std::move(inByteCode))) { } - ShaderModuleCreateInfo& ShaderModuleCreateInfo::SetByteCode(const void* inByteCode) + ShaderModuleCreateInfo& ShaderModuleCreateInfo::SetByteCode(ShaderByteCodeRef inByteCode) + { + byteCode = std::move(inByteCode); + return *this; + } + + ShaderModuleCreateInfo& ShaderModuleCreateInfo::SetByteCode(const void* inByteCode, const size_t inSize) + { + byteCode = Common::MakeShared(inByteCode, inSize); + return *this; + } + + ShaderModuleCreateInfo& ShaderModuleCreateInfo::SetByteCode(const std::vector& inByteCode) { - byteCode = inByteCode; + byteCode = Common::MakeShared(inByteCode); return *this; } - ShaderModuleCreateInfo& ShaderModuleCreateInfo::SetSize(const size_t inSize) + ShaderModuleCreateInfo& ShaderModuleCreateInfo::SetByteCode(std::vector&& inByteCode) { - size = inSize; + byteCode = Common::MakeShared(std::move(inByteCode)); return *this; } diff --git a/Engine/Source/Render/Include/Render/Shader.h b/Engine/Source/Render/Include/Render/Shader.h index 9c4d7737c..6cd17054e 100644 --- a/Engine/Source/Render/Include/Render/Shader.h +++ b/Engine/Source/Render/Include/Render/Shader.h @@ -306,7 +306,7 @@ namespace Render { struct ShaderVariantArtifact { std::string entryPoint; - std::vector byteCode; + RHI::ShaderByteCodeRef byteCode; ShaderReflectionData reflectionData; }; diff --git a/Engine/Source/Render/Include/Render/ShaderCompiler.h b/Engine/Source/Render/Include/Render/ShaderCompiler.h index 83b9076aa..c19a9685e 100644 --- a/Engine/Source/Render/Include/Render/ShaderCompiler.h +++ b/Engine/Source/Render/Include/Render/ShaderCompiler.h @@ -35,7 +35,7 @@ namespace Render { struct ShaderCompileOutput { bool success; std::string entryPoint; - std::vector byteCode; + RHI::ShaderByteCodeRef byteCode; ShaderReflectionData reflectionData; std::string errorInfo; }; diff --git a/Engine/Source/Render/SharedSrc/RenderModule.cpp b/Engine/Source/Render/SharedSrc/RenderModule.cpp index 26452f021..47f8833c9 100644 --- a/Engine/Source/Render/SharedSrc/RenderModule.cpp +++ b/Engine/Source/Render/SharedSrc/RenderModule.cpp @@ -40,7 +40,9 @@ namespace Render { RenderWorkerThreads::Get().Start(); rhiInstance = RHI::Instance::GetByType(inParams.rhiType, inParams.instanceCreateInfo); - rhiDevice = rhiInstance->GetGpu(0)->RequestDevice( + AssertWithReason(rhiInstance->GetGpuNum() > 0, "no compatible GPU was found"); + RHI::Gpu* gpu = rhiInstance->GetGpu(0); + rhiDevice = gpu->RequestDevice( RHI::DeviceCreateInfo() .AddQueueRequest(RHI::QueueRequestInfo(RHI::QueueType::graphics, 1)) .AddQueueRequest(RHI::QueueRequestInfo(RHI::QueueType::compute, 1)) diff --git a/Engine/Source/Render/Src/RenderCache.cpp b/Engine/Source/Render/Src/RenderCache.cpp index ef573a078..bf6b4ec74 100644 --- a/Engine/Source/Render/Src/RenderCache.cpp +++ b/Engine/Source/Render/Src/RenderCache.cpp @@ -43,6 +43,19 @@ namespace Render::Internal { }); } + static uint64_t HashRhiState(const RHI::PrimitiveState& state) + { + return CombineHashes({ + static_cast(state.topologyType), + static_cast(state.fillMode), + static_cast(state.stripIndexFormat), + static_cast(state.frontFace), + static_cast(state.cullMode), + static_cast(state.depthClip), + static_cast(state.patchControlPoints) + }); + } + static uint64_t HashRhiState(const RHI::DepthStencilState& state) { return CombineHashes({ diff --git a/Engine/Source/Render/Src/ShaderCompiler.cpp b/Engine/Source/Render/Src/ShaderCompiler.cpp index abe08b64a..d7f4bc016 100644 --- a/Engine/Source/Render/Src/ShaderCompiler.cpp +++ b/Engine/Source/Render/Src/ShaderCompiler.cpp @@ -321,8 +321,8 @@ namespace Render { output.success = true; const auto* codeStart = static_cast(codeBlob->GetBufferPointer()); const auto* codeEnd = codeStart + codeBlob->GetBufferSize(); + std::vector byteCode(codeStart, codeEnd); output.entryPoint = input.entryPoint; - output.byteCode = std::vector(codeStart, codeEnd); if (options.byteCodeType == ShaderByteCodeType::dxil) { #if PLATFORM_WINDOWS @@ -339,9 +339,11 @@ namespace Render { BuildHlslReflectionData(shaderReflection, output.reflectionData); #endif } else { - const spirv_cross::Compiler sprivCrossCompiler(reinterpret_cast(output.byteCode.data()), output.byteCode.size() * sizeof(uint8_t) / sizeof(uint32_t)); + const spirv_cross::Compiler sprivCrossCompiler(reinterpret_cast(byteCode.data()), byteCode.size() * sizeof(uint8_t) / sizeof(uint32_t)); BuildGlslReflectionData(sprivCrossCompiler, output.reflectionData); } + + output.byteCode = Common::MakeShared(std::move(byteCode)); } } diff --git a/Engine/Source/Runtime/Include/Runtime/Component/Name.h b/Engine/Source/Runtime/Include/Runtime/Component/Name.h index cd45897a8..0b6290bb2 100644 --- a/Engine/Source/Runtime/Include/Runtime/Component/Name.h +++ b/Engine/Source/Runtime/Include/Runtime/Component/Name.h @@ -10,7 +10,7 @@ #include namespace Runtime { - struct RUNTIME_API EClass(comp) Name final { + struct RUNTIME_API EClass(comp, editorHide) Name final { EClassBody(Name) Name(); diff --git a/Engine/Source/Runtime/Include/Runtime/Engine.h b/Engine/Source/Runtime/Include/Runtime/Engine.h index afbeef020..790a7144a 100644 --- a/Engine/Source/Runtime/Include/Runtime/Engine.h +++ b/Engine/Source/Runtime/Include/Runtime/Engine.h @@ -14,11 +14,14 @@ namespace Runtime { class World; - struct EngineInitParams { + struct RUNTIME_API EngineInitParams { + EngineInitParams(); + bool logToFile; bool gpuDebug; std::string gameRoot; std::string rhiType; + bool useSoftwareGpu; }; class RUNTIME_API Engine { // NOLINT @@ -36,7 +39,7 @@ namespace Runtime { explicit Engine(const EngineInitParams& inParams); void AttachLogFile() const; - void InitRender(const std::string& inRhiTypeStr, bool inGpuDebug); + void InitRender(const std::string& inRhiTypeStr, bool inGpuDebug, bool inUseSoftwareGpu); void LoadPlugins() const; void LoadConfigs() const; diff --git a/Engine/Source/Runtime/Include/Runtime/Meta.h b/Engine/Source/Runtime/Include/Runtime/Meta.h index f2d08aace..ab9c5e5ba 100644 --- a/Engine/Source/Runtime/Include/Runtime/Meta.h +++ b/Engine/Source/Runtime/Include/Runtime/Meta.h @@ -9,6 +9,7 @@ namespace Runtime { struct RUNTIME_API MetaPresets { + static constexpr const auto* editorHide = "editorHide"; static constexpr const auto* globalComp = "globalComp"; static constexpr const auto* gameReadOnly = "gameReadOnly"; static constexpr const auto* tag = "tag"; diff --git a/Engine/Source/Runtime/Src/Asset/Texture.cpp b/Engine/Source/Runtime/Src/Asset/Texture.cpp index 0083607e6..671d65d23 100644 --- a/Engine/Source/Runtime/Src/Asset/Texture.cpp +++ b/Engine/Source/Runtime/Src/Asset/Texture.cpp @@ -56,6 +56,36 @@ namespace Runtime::Internal { { return inMipLevel * inTotalArrayLayer + inArrayLayer; // NOLINT } + + static void CopyDepthStencilSubResourceToStaging(const TextureFormat format, const std::vector& srcPixels, uint8_t* dstData, const RHI::TextureSubResourceCopyFootprint& footprint) + { + Assert(IsDepthAndStencilFormat(format)); + const auto srcBytesPerPixel = RHI::GetBytesPerPixel(static_cast(format)); + const auto srcRowPitch = footprint.extent.x * srcBytesPerPixel; + const auto srcSlicePitch = srcRowPitch * footprint.extent.y; + const auto planeBytes = footprint.slicePitch * footprint.extent.z; + auto* dstStencilPlane = dstData + planeBytes; + + for (auto z = 0u; z < footprint.extent.z; z++) { + for (auto y = 0u; y < footprint.extent.y; y++) { + const auto* srcRow = srcPixels.data() + srcSlicePitch * z + srcRowPitch * y; + auto* dstDepthRow = dstData + footprint.slicePitch * z + footprint.rowPitch * y; + auto* dstStencilRow = dstStencilPlane + footprint.slicePitch * z + footprint.rowPitch * y; + for (auto x = 0u; x < footprint.extent.x; x++) { + const auto* srcPixel = srcRow + srcBytesPerPixel * x; + auto* dstDepthPixel = dstDepthRow + 4 * x; + if (format == TextureFormat::d24UnormS8Uint) { + memcpy(dstDepthPixel, srcPixel, 3); + dstDepthPixel[3] = 0; + dstStencilRow[x] = srcPixel[3]; + } else { + memcpy(dstDepthPixel, srcPixel, 4); + dstStencilRow[x] = srcPixel[4]; + } + } + } + } + } } namespace Runtime { @@ -282,6 +312,11 @@ namespace Runtime { const auto& dstCopyFootprint = copyFootprints[subResourceIndex]; const auto dstSubResourceOffset = copyOffsets[subResourceIndex]; + if (Internal::IsDepthAndStencilFormat(format)) { + Internal::CopyDepthStencilSubResourceToStaging(format, srcPixels, dstData + dstSubResourceOffset, dstCopyFootprint); + continue; + } + const auto srcRowPitch = dstCopyFootprint.extent.x * bytesPerPixel; const auto srcSlicePitch = srcRowPitch * dstCopyFootprint.extent.y; for (auto z = 0u; z < dstCopyFootprint.extent.z; z++) { @@ -308,6 +343,8 @@ namespace Runtime { texturePtr, RHI::BufferTextureCopyInfo() .SetBufferOffset(copyOffsets[subResourceIndex]) + .SetBufferRowPitch(copyFootprints[subResourceIndex].rowPitch) + .SetBufferSlicePitch(copyFootprints[subResourceIndex].slicePitch) .SetTextureSubResource(RHI::TextureSubResourceInfo(m, a, aspect)) .SetTextureOrigin({ 0, 0, 0 }) .SetCopyRegion(copyFootprints[subResourceIndex].extent)); diff --git a/Engine/Source/Runtime/Src/Engine.cpp b/Engine/Source/Runtime/Src/Engine.cpp index 3610fe035..ef6a51da3 100644 --- a/Engine/Source/Runtime/Src/Engine.cpp +++ b/Engine/Source/Runtime/Src/Engine.cpp @@ -16,6 +16,15 @@ #include namespace Runtime { + EngineInitParams::EngineInitParams() + : logToFile(false) + , gpuDebug(false) + , gameRoot() + , rhiType(RHI::GetPlatformDefaultRHIAbbrString()) + , useSoftwareGpu(false) + { + } + Engine::Engine(const EngineInitParams& inParams) { Core::ThreadContext::SetTag(Core::ThreadTag::game); @@ -28,7 +37,7 @@ namespace Runtime { if (inParams.logToFile) { AttachLogFile(); } - InitRender(inParams.rhiType, inParams.gpuDebug); + InitRender(inParams.rhiType, inParams.gpuDebug, inParams.useSoftwareGpu); LoadPlugins(); LoadConfigs(); } @@ -94,18 +103,21 @@ namespace Runtime { LogInfo(Core, "logger attached to file {}", logFile); } - void Engine::InitRender(const std::string& inRhiTypeStr, bool inGpuDebug) + void Engine::InitRender(const std::string& inRhiTypeStr, bool inGpuDebug, const bool inUseSoftwareGpu) { renderModule = ::Core::ModuleManager::Get().FindOrLoadTyped("Render"); Assert(renderModule != nullptr); Render::RenderModuleInitParams initParams; initParams.rhiType = RHI::GetRHITypeByAbbrString(inRhiTypeStr); + initParams.instanceCreateInfo.useSoftwareGpu = inUseSoftwareGpu; #if BUILD_CONFIG_DEBUG initParams.instanceCreateInfo.gpuDebug = inGpuDebug; #endif renderModule->Initialize(initParams); LogInfo(Render, "RHI type: {}", inRhiTypeStr); + const auto gpuProperty = renderModule->GetDevice()->GetGpu().GetProperty(); + LogInfo(Render, "GPU: {} ({})", gpuProperty.name, gpuProperty.type == RHI::GpuType::software ? "software" : "hardware"); } void Engine::LoadPlugins() const // NOLINT diff --git a/README.md b/README.md index 64ecd33f6..2c4c46727 100644 --- a/README.md +++ b/README.md @@ -125,6 +125,7 @@ Thanks all those following projects: * [Conan](https://github.com/conan-io/conan) * [DirectX-Headers](https://github.com/microsoft/DirectX-Headers) * [Vulkan](https://www.vulkan.org/) +* [SwiftShader](https://github.com/google/swiftshader) * [DirectXShaderCompiler](https://github.com/microsoft/DirectXShaderCompiler) * [GLFW](https://www.glfw.org/) * [Dear ImGui](https://github.com/ocornut/imgui) diff --git a/Sample/Base/Application.cpp b/Sample/Base/Application.cpp index b10662b40..f80e725b7 100644 --- a/Sample/Base/Application.cpp +++ b/Sample/Base/Application.cpp @@ -6,6 +6,7 @@ #include #include +#include #include #include #include @@ -25,6 +26,9 @@ Application::Application(std::string n) , windowExtent(1024, 768) , rhiType(RHI::RHIType::vulkan) , instance(nullptr) + , gpu(nullptr) + , headless(false) + , outputPath() , mousePos(FVec2Consts::zero) , mouseButtonsStatus() , lastTimeSeconds(TimePoint::Now().ToSeconds()) @@ -44,12 +48,16 @@ bool Application::Initialize(int argc, char* argv[]) Core::Cli::Get().Parse(argc, argv); std::string rhiString; + bool softwareGpu = false; #if BUILD_CONFIG_DEBUG bool gpuDebug = false; #endif if (const auto cli = ( clipp::option("-w").doc("window width, 1024 by default") & clipp::value("width", windowExtent.x), clipp::option("-h").doc("window height, 768 by default") & clipp::value("height", windowExtent.y), + clipp::option("-headless", "--headless").set(headless).doc("render one frame without creating a window"), + clipp::option("-softwareGpu", "--software-gpu").set(softwareGpu).doc("use the software GPU driver"), + clipp::option("-output", "--output").doc("headless output image path, including extension") & clipp::value("path", outputPath), #if BUILD_CONFIG_DEBUG clipp::option("-gpuDebug").set(gpuDebug).doc("enable GPU validation layers"), #endif @@ -60,24 +68,44 @@ bool Application::Initialize(int argc, char* argv[]) return false; } + if (headless && outputPath.empty()) { + std::cerr << "headless mode requires -output with a complete image path" << std::endl; + return false; + } + if (headless && !IsSupportedSampleImageOutputPath(outputPath)) { + std::cerr << "unsupported output image extension; expected .png, .bmp, .tga, .jpg, or .jpeg" << std::endl; + return false; + } + rhiType = RHI::GetRHITypeByAbbrString(rhiString); RHI::InstanceCreateInfo instanceCreateInfo; + instanceCreateInfo.useSoftwareGpu = softwareGpu; #if BUILD_CONFIG_DEBUG instanceCreateInfo.gpuDebug = gpuDebug; #endif instance = RHI::Instance::GetByType(rhiType, instanceCreateInfo); + if (instance->GetGpuNum() == 0) { + std::cerr << "no compatible GPU was found" << std::endl; + return false; + } + gpu = instance->GetGpu(0); + const auto gpuProperty = gpu->GetProperty(); + std::cout << "Selected GPU: " << gpuProperty.name << " (" << (gpuProperty.type == RHI::GpuType::software ? "software" : "hardware") << ")" << std::endl; return true; } int Application::RunLoop() { - glfwInit(); - glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API); - window = glfwCreateWindow(static_cast(windowExtent.x), static_cast(windowExtent.y), name.c_str(), nullptr, nullptr); + if (!headless) { + Assert(glfwInit() == GLFW_TRUE); + glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API); + window = glfwCreateWindow(static_cast(windowExtent.x), static_cast(windowExtent.y), name.c_str(), nullptr, nullptr); + Assert(window != nullptr); + } OnCreate(); - if (camera != nullptr) { + if (!headless && camera != nullptr) { auto keyCallback = [](GLFWwindow* inWindow, int key, int scancode, int action, int mods) -> void { const auto* app = static_cast(glfwGetWindowUserPointer(inWindow)); app->OnKeyActionReceived(key, action); @@ -97,7 +125,13 @@ int Application::RunLoop() glfwSetMouseButtonCallback(window, mouseButtonCallback); } - while (!static_cast(glfwWindowShouldClose(window))) { + if (headless) { + currentTimeSeconds = 0.0; + deltaTimeSeconds = 0.0f; + OnDrawFrame(); + } + + while (!headless && !static_cast(glfwWindowShouldClose(window))) { currentTimeSeconds = TimePoint::Now().ToSeconds(); deltaTimeSeconds = static_cast(currentTimeSeconds - lastTimeSeconds); lastTimeSeconds = currentTimeSeconds; @@ -109,8 +143,10 @@ int Application::RunLoop() } OnDestroy(); - glfwDestroyWindow(window); - glfwTerminate(); + if (!headless) { + glfwDestroyWindow(window); + glfwTerminate(); + } return 0; } @@ -247,6 +283,16 @@ uint32_t Application::GetWindowHeight() const return windowExtent.y; } +bool Application::IsHeadless() const +{ + return headless; +} + +const std::string& Application::GetOutputPath() const +{ + return outputPath; +} + RHI::RHIType Application::GetRHIType() const { return rhiType; @@ -257,6 +303,11 @@ RHI::Instance* Application::GetRHIInstance() const return instance; } +RHI::Gpu* Application::GetGpu() const +{ + return gpu; +} + void Application::SetCamera(Camera* inCamera) { camera = inCamera; @@ -267,6 +318,14 @@ Camera& Application::GetCamera() const return *camera; } +UniquePtr Application::CreateRenderTarget(RHI::Device& device) const +{ + if (headless) { + return new HeadlessRenderTarget(device, windowExtent.x, windowExtent.y, outputPath); + } + return new SwapChainRenderTarget(device, windowExtent.x, windowExtent.y, GetPlatformWindow()); +} + Application::ShaderCompileOutput Application::CompileShader(const std::string& fileName, const std::string& entryPoint, RHI::ShaderStageBits shaderStage, std::vector includePaths) const { std::string shaderSource = FileUtils::ReadTextFile(fileName).Unwrap(); diff --git a/Sample/Base/Application.h b/Sample/Base/Application.h index 28035ed37..71334edbb 100644 --- a/Sample/Base/Application.h +++ b/Sample/Base/Application.h @@ -20,6 +20,8 @@ #include #include +class SampleRenderTarget; + class Application { public: enum class MouseButton : uint8_t { @@ -30,7 +32,7 @@ class Application { }; struct ShaderCompileOutput { - std::vector byteCode; + RHI::ShaderByteCodeRef byteCode; Render::ShaderReflectionData reflectionData; }; @@ -57,9 +59,13 @@ class Application { void* GetPlatformWindow() const; uint32_t GetWindowWidth() const; uint32_t GetWindowHeight() const; + bool IsHeadless() const; + const std::string& GetOutputPath() const; RHI::RHIType GetRHIType() const; RHI::Instance* GetRHIInstance() const; + RHI::Gpu* GetGpu() const; Camera& GetCamera() const; + UniquePtr CreateRenderTarget(RHI::Device& device) const; ShaderCompileOutput CompileShader(const std::string& fileName, const std::string& entryPoint, RHI::ShaderStageBits shaderStage, std::vector includePaths = {}) const; private: @@ -68,6 +74,9 @@ class Application { UVec2 windowExtent; RHI::RHIType rhiType; RHI::Instance* instance; + RHI::Gpu* gpu; + bool headless; + std::string outputPath; UniquePtr camera; FVec2 mousePos; std::array(MouseButton::max)> mouseButtonsStatus; diff --git a/Sample/Base/RenderTarget.cpp b/Sample/Base/RenderTarget.cpp new file mode 100644 index 000000000..8ba757b54 --- /dev/null +++ b/Sample/Base/RenderTarget.cpp @@ -0,0 +1,230 @@ +#include +#include +#include +#include +#include +#include +#include + +#define STB_IMAGE_WRITE_IMPLEMENTATION +#include + +#include + +using namespace Common; +using namespace Render; +using namespace RHI; + +namespace Sample::Internal { + static std::string GetLowercaseExtension(const std::string& path) + { + auto extension = std::filesystem::path(path).extension().string(); + std::ranges::transform(extension, extension.begin(), [](const unsigned char character) -> char { return static_cast(std::tolower(character)); }); + return extension; + } + + static int WriteImage(const std::string& path, const int width, const int height, const uint8_t* pixels) + { + const auto extension = GetLowercaseExtension(path); + if (extension == ".png") { + return stbi_write_png(path.c_str(), width, height, 4, pixels, width * 4); + } + if (extension == ".bmp") { + return stbi_write_bmp(path.c_str(), width, height, 4, pixels); + } + if (extension == ".tga") { + return stbi_write_tga(path.c_str(), width, height, 4, pixels); + } + if (extension == ".jpg" || extension == ".jpeg") { + return stbi_write_jpg(path.c_str(), width, height, 4, pixels, 90); + } + return 0; + } +} + +SampleRenderTarget::SampleRenderTarget(Device& inDevice, const uint32_t inWidth, const uint32_t inHeight) + : device(inDevice) + , width(inWidth) + , height(inHeight) +{ +} + +SampleRenderTarget::~SampleRenderTarget() = default; + +SwapChainRenderTarget::SwapChainRenderTarget(Device& inDevice, const uint32_t inWidth, const uint32_t inHeight, void* inPlatformWindow) + : SampleRenderTarget(inDevice, inWidth, inHeight) + , format(PixelFormat::max) + , surface(device.CreateSurface(SurfaceCreateInfo(inPlatformWindow))) + , textures() + , textureStates() +{ +} + +SwapChainRenderTarget::~SwapChainRenderTarget() = default; + +void SwapChainRenderTarget::Initialize() +{ + static const std::array formatQualifiers = { + PixelFormat::rgba8Unorm, + PixelFormat::bgra8Unorm + }; + + for (const auto candidate : formatQualifiers) { + if (device.CheckSwapChainFormatSupport(surface.Get(), candidate, ColorSpace::srgbNonLinear)) { + format = candidate; + break; + } + } + Assert(format != PixelFormat::max); + + swapChain = device.CreateSwapChain( + SwapChainCreateInfo() + .SetFormat(format) + .SetPresentMode(PresentMode::immediately) + .SetTextureNum(backBufferCount) + .SetWidth(width) + .SetHeight(height) + .SetSurface(surface.Get()) + .SetPresentQueue(device.GetQueue(QueueType::graphics, 0))); + + for (auto i = 0; i < backBufferCount; i++) { + textures[i] = swapChain->GetTexture(i); + textureStates[i] = textures[i]->GetCreateInfo().initialState; + renderFinishedSemaphores[i] = device.CreateSemaphore(); + } + imageReadySemaphore = device.CreateSemaphore(); + frameFence = device.CreateFence(true); +} + +SampleRenderTarget::Frame SwapChainRenderTarget::Acquire() +{ + frameFence->Reset(); + const auto textureIndex = swapChain->AcquireBackTexture(imageReadySemaphore.Get()); + return { textures[textureIndex], textureStates[textureIndex], textureIndex }; +} + +void SwapChainRenderTarget::FinishRenderPass(const RGBuilder& builder, CommandRecorder& recorder, const RGTextureRef outputTexture) const +{ + recorder.ResourceBarrier(Barrier::Transition(builder.GetRHI(outputTexture), TextureState::renderTarget, TextureState::present)); +} + +void SwapChainRenderTarget::PrepareForSubmit(RGBuilder&, RGTextureRef) +{ +} + +void SwapChainRenderTarget::Execute(RGBuilder& builder, const Frame& frame) +{ + RGExecuteInfo executeInfo; + executeInfo.semaphoresToWait = { imageReadySemaphore.Get() }; + executeInfo.semaphoresToSignal = { renderFinishedSemaphores[frame.textureIndex].Get() }; + executeInfo.inFenceToSignal = frameFence.Get(); + builder.Execute(executeInfo); + + swapChain->Present(renderFinishedSemaphores[frame.textureIndex].Get()); + textureStates[frame.textureIndex] = TextureState::present; + frameFence->Wait(); +} + +PixelFormat SwapChainRenderTarget::GetFormat() const +{ + return format; +} + +HeadlessRenderTarget::HeadlessRenderTarget(Device& inDevice, const uint32_t inWidth, const uint32_t inHeight, std::string inOutputPath) + : SampleRenderTarget(inDevice, inWidth, inHeight) + , outputPath(std::move(inOutputPath)) + , textureState(TextureState::undefined) + , copyFootprint() +{ +} + +HeadlessRenderTarget::~HeadlessRenderTarget() = default; + +void HeadlessRenderTarget::Initialize() +{ + texture = device.CreateTexture( + TextureCreateInfo() + .SetType(TextureType::t2D) + .SetWidth(width) + .SetHeight(height) + .SetDepthOrArraySize(1) + .SetFormat(GetFormat()) + .SetUsages(TextureUsageBits::renderAttachment | TextureUsageBits::copySrc) + .SetMipLevels(1) + .SetSamples(1) + .SetInitialState(TextureState::renderTarget) + .SetDebugName("HeadlessOutput")); + textureState = TextureState::renderTarget; + copyFootprint = device.GetTextureSubResourceCopyFootprint(*texture, TextureSubResourceInfo()); + readbackBuffer = device.CreateBuffer( + BufferCreateInfo() + .SetSize(copyFootprint.totalBytes) + .SetUsages(BufferUsageBits::mapRead | BufferUsageBits::copyDst) + .SetInitialState(BufferState::copyDst) + .SetDebugName("HeadlessOutputReadback")); + frameFence = device.CreateFence(true); +} + +SampleRenderTarget::Frame HeadlessRenderTarget::Acquire() +{ + frameFence->Reset(); + return { texture.Get(), textureState, 0 }; +} + +void HeadlessRenderTarget::FinishRenderPass(const RGBuilder&, CommandRecorder&, RGTextureRef) const +{ +} + +void HeadlessRenderTarget::PrepareForSubmit(RGBuilder& builder, const RGTextureRef outputTexture) +{ + auto* readback = builder.ImportBuffer(readbackBuffer.Get(), BufferState::copyDst); + RGCopyPassDesc copyDesc; + copyDesc.copySrcs = { outputTexture }; + copyDesc.copyDsts = { readback }; + builder.AddCopyPass( + "ReadbackOutput", + copyDesc, + [outputTexture, readback, copyRegion = UVec3(width, height, 1), rowPitch = copyFootprint.rowPitch, slicePitch = copyFootprint.slicePitch](const RGBuilder& rg, CopyPassCommandRecorder& recorder) -> void { + recorder.CopyTextureToBuffer(rg.GetRHI(outputTexture), rg.GetRHI(readback), BufferTextureCopyInfo(0, TextureSubResourceInfo(), UVec3Consts::zero, copyRegion, rowPitch, slicePitch)); + }); +} + +void HeadlessRenderTarget::Execute(RGBuilder& builder, const Frame&) +{ + RGExecuteInfo executeInfo; + executeInfo.inFenceToSignal = frameFence.Get(); + builder.Execute(executeInfo); + + textureState = TextureState::copySrc; + frameFence->Wait(); + SaveOutput(); +} + +PixelFormat HeadlessRenderTarget::GetFormat() const +{ + return PixelFormat::rgba8Unorm; +} + +void HeadlessRenderTarget::SaveOutput() const +{ + const auto rowSize = width * copyFootprint.bytesPerPixel; + std::vector pixels(rowSize * height); + const auto* mapped = static_cast(readbackBuffer->Map(MapMode::read, 0, copyFootprint.totalBytes)); + for (uint32_t row = 0; row < height; row++) { + std::memcpy(pixels.data() + row * rowSize, mapped + row * copyFootprint.rowPitch, rowSize); + } + readbackBuffer->Unmap(); + + const std::filesystem::path path(outputPath); + if (path.has_parent_path()) { + std::filesystem::create_directories(path.parent_path()); + } + Assert(Sample::Internal::WriteImage(outputPath, static_cast(width), static_cast(height), pixels.data()) != 0); + std::cout << "Saved headless output to " << std::filesystem::absolute(path).string() << std::endl; +} + +bool IsSupportedSampleImageOutputPath(const std::string& path) +{ + const auto extension = Sample::Internal::GetLowercaseExtension(path); + return extension == ".png" || extension == ".bmp" || extension == ".tga" || extension == ".jpg" || extension == ".jpeg"; +} diff --git a/Sample/Base/RenderTarget.h b/Sample/Base/RenderTarget.h new file mode 100644 index 000000000..fa489ea59 --- /dev/null +++ b/Sample/Base/RenderTarget.h @@ -0,0 +1,88 @@ +#pragma once + +#include +#include +#include +#include + +#include +#include +#include + +class SampleRenderTarget { +public: + struct Frame { + RHI::Texture* texture; + RHI::TextureState initialState; + uint8_t textureIndex; + }; + + NonCopyable(SampleRenderTarget) + virtual ~SampleRenderTarget(); + + virtual void Initialize() = 0; + virtual Frame Acquire() = 0; + virtual void FinishRenderPass(const Render::RGBuilder& builder, RHI::CommandRecorder& recorder, Render::RGTextureRef outputTexture) const = 0; + virtual void PrepareForSubmit(Render::RGBuilder& builder, Render::RGTextureRef outputTexture) = 0; + virtual void Execute(Render::RGBuilder& builder, const Frame& frame) = 0; + virtual RHI::PixelFormat GetFormat() const = 0; + +protected: + SampleRenderTarget(RHI::Device& inDevice, uint32_t inWidth, uint32_t inHeight); + + RHI::Device& device; + uint32_t width; + uint32_t height; +}; + +class SwapChainRenderTarget final : public SampleRenderTarget { +public: + NonCopyable(SwapChainRenderTarget) + SwapChainRenderTarget(RHI::Device& inDevice, uint32_t inWidth, uint32_t inHeight, void* inPlatformWindow); + ~SwapChainRenderTarget() override; + + void Initialize() override; + Frame Acquire() override; + void FinishRenderPass(const Render::RGBuilder& builder, RHI::CommandRecorder& recorder, Render::RGTextureRef outputTexture) const override; + void PrepareForSubmit(Render::RGBuilder& builder, Render::RGTextureRef outputTexture) override; + void Execute(Render::RGBuilder& builder, const Frame& frame) override; + RHI::PixelFormat GetFormat() const override; + +private: + static constexpr size_t backBufferCount = 2; + + RHI::PixelFormat format; + Common::UniquePtr surface; + Common::UniquePtr swapChain; + std::array textures; + std::array textureStates; + Common::UniquePtr imageReadySemaphore; + std::array, backBufferCount> renderFinishedSemaphores; + Common::UniquePtr frameFence; +}; + +class HeadlessRenderTarget final : public SampleRenderTarget { +public: + NonCopyable(HeadlessRenderTarget) + HeadlessRenderTarget(RHI::Device& inDevice, uint32_t inWidth, uint32_t inHeight, std::string inOutputPath); + ~HeadlessRenderTarget() override; + + void Initialize() override; + Frame Acquire() override; + void FinishRenderPass(const Render::RGBuilder& builder, RHI::CommandRecorder& recorder, Render::RGTextureRef outputTexture) const override; + void PrepareForSubmit(Render::RGBuilder& builder, Render::RGTextureRef outputTexture) override; + void Execute(Render::RGBuilder& builder, const Frame& frame) override; + RHI::PixelFormat GetFormat() const override; + +private: + void SaveOutput() const; + + std::string outputPath; + Common::UniquePtr texture; + RHI::TextureState textureState; + RHI::TextureSubResourceCopyFootprint copyFootprint; + Common::UniquePtr readbackBuffer; + Common::UniquePtr frameFence; +}; + +bool IsSupportedSampleImageOutputPath(const std::string& path); diff --git a/Sample/CMakeLists.txt b/Sample/CMakeLists.txt index 46f6efbcc..ef85b41e3 100644 --- a/Sample/CMakeLists.txt +++ b/Sample/CMakeLists.txt @@ -84,3 +84,41 @@ add_sample( MODEL Rendering-SSAO/Model/Voyager.gltf ) + +if (BUILD_TEST AND BUILD_SAMPLE) + exp_add_executable( + NAME RenderingSample.Test + FOLDER Test + SRC Test/RenderingSampleTest.cpp + LIB Common cimg::cimg stb::stb + DEP_TARGET RenderingSample-Triangle RenderingSample-BaseTexture RenderingSample-SSAO + PRIVATE_COMPILE_DEF cimg_display=0 CI=$ + NOT_INSTALL + ) + + function(exp_add_rendering_sample_test) + set(options "") + set(singleValueArgs SAMPLE BASELINE RHI) + set(multiValueArgs "") + cmake_parse_arguments(arg "${options}" "${singleValueArgs}" "${multiValueArgs}" ${ARGN}) + + set(test_name "${arg_SAMPLE}.${arg_RHI}.Test") + add_test( + NAME ${test_name} + COMMAND RenderingSample.Test + --sample $ + --rhi ${arg_RHI} + --baseline ${CMAKE_CURRENT_SOURCE_DIR}/Test/Baseline/${arg_BASELINE}/baseline.png + --output-dir ${CMAKE_BINARY_DIR}/Test/Generated/RenderingSample/${arg_SAMPLE}/${arg_RHI} + ) + set_tests_properties(${test_name} PROPERTIES WORKING_DIRECTORY $) + endfunction() + + set(rendering_sample_names RenderingSample-Triangle RenderingSample-BaseTexture RenderingSample-SSAO) + foreach(sample ${rendering_sample_names}) + exp_add_rendering_sample_test(SAMPLE ${sample} BASELINE ${sample} RHI vulkan) + if (WIN32) + exp_add_rendering_sample_test(SAMPLE ${sample} BASELINE ${sample} RHI dx12) + endif () + endforeach() +endif () diff --git a/Sample/Rendering-BaseTexture/BaseTexture.cpp b/Sample/Rendering-BaseTexture/BaseTexture.cpp index 6ecf498b8..7e44551b7 100644 --- a/Sample/Rendering-BaseTexture/BaseTexture.cpp +++ b/Sample/Rendering-BaseTexture/BaseTexture.cpp @@ -3,6 +3,7 @@ // #include +#include #define STB_IMAGE_IMPLEMENTATION #include #include @@ -60,42 +61,27 @@ class BaseTexApp final : public Application { void OnDestroy() override; private: - static constexpr size_t backBufferCount = 2; - void CreateDevice(); - void CreateSurface(); void CompileAllShaders() const; void FetchShaderInstances(); - void CreateSwapChain(); void CreateVertexAndIndexBuffer(); void CreateTextureAndSampler(); - void CreateSyncObjects(); - PixelFormat swapChainFormat; ShaderInstance vs; ShaderInstance ps; UniquePtr device; - UniquePtr surface; - UniquePtr swapChain; + UniquePtr renderTarget; UniquePtr sampler; UniquePtr vertexBuffer; UniquePtr indexBuffer; UniquePtr uniformBuffer; UniquePtr texture; UniquePtr imageBuffer; - std::array swapChainTextures; - std::array swapChainTextureStates; UniquePtr pipeline; - UniquePtr imageReadySemaphore; - std::array, backBufferCount> renderFinishedSemaphores; - UniquePtr frameFence; }; BaseTexApp::BaseTexApp(const std::string& inName) : Application(inName) - , swapChainFormat(PixelFormat::max) - , swapChainTextures() - , swapChainTextureStates() { } @@ -108,22 +94,20 @@ void BaseTexApp::OnCreate() RenderWorkerThreads::Get().Start(); CreateDevice(); - CreateSurface(); + renderTarget = CreateRenderTarget(*device); RenderThread::Get().EmplaceTask([this]() -> void { FetchShaderInstances(); - CreateSwapChain(); + renderTarget->Initialize(); CreateVertexAndIndexBuffer(); CreateTextureAndSampler(); - CreateSyncObjects(); }); } void BaseTexApp::OnDrawFrame() { RenderThread::Get().EmplaceTask([this]() -> void { - frameFence->Reset(); - const auto backTextureIndex = swapChain->AcquireBackTexture(imageReadySemaphore.Get()); + const auto frame = renderTarget->Acquire(); auto* pso = Render::PipelineCache::Get(*device).GetOrCreate( RasterPipelineStateDesc() @@ -137,11 +121,11 @@ void BaseTexApp::OnDrawFrame() .AddAttribute(RVertexAttribute(RVertexBinding("TEXCOORD", 0), VertexFormat::float32X2, offsetof(Vertex, uv))))) .SetFragmentState( RFragmentState() - .AddColorTarget(ColorTargetState(swapChainFormat, ColorWriteBits::all, false))) + .AddColorTarget(ColorTargetState(renderTarget->GetFormat(), ColorWriteBits::all, false))) .SetPrimitiveState(PrimitiveState(PrimitiveTopologyType::triangle, FillMode::solid, IndexFormat::uint16, FrontFace::ccw, CullMode::none))); RGBuilder builder(*device); - auto* backTexture = builder.ImportTexture(swapChainTextures[backTextureIndex], swapChainTextureStates[backTextureIndex]); + auto* backTexture = builder.ImportTexture(frame.texture, frame.initialState); auto* backTextureView = builder.CreateTextureView(backTexture, RGTextureViewDesc(TextureViewType::colorAttachment, TextureViewDimension::tv2D)); auto* vBuffer = builder.ImportBuffer(vertexBuffer.Get(), BufferState::shaderReadOnly); auto* vBufferView = builder.CreateBufferView(vBuffer, RGBufferViewDesc(BufferViewType::vertex, vBuffer->GetDesc().size, 0, VertexBufferViewInfo(sizeof(Vertex)))); @@ -181,18 +165,12 @@ void BaseTexApp::OnDrawFrame() recorder.DrawIndexed(6, 1, 0, 0, 0); }, {}, - [backTexture](const RGBuilder& rg, CommandRecorder& recorder) -> void { - recorder.ResourceBarrier(Barrier::Transition(rg.GetRHI(backTexture), TextureState::renderTarget, TextureState::present)); + [this, backTexture](const RGBuilder& rg, CommandRecorder& recorder) -> void { + renderTarget->FinishRenderPass(rg, recorder, backTexture); }); - RGExecuteInfo executeInfo; - executeInfo.semaphoresToWait = { imageReadySemaphore.Get() }; - executeInfo.semaphoresToSignal = { renderFinishedSemaphores[backTextureIndex].Get() }; - executeInfo.inFenceToSignal = frameFence.Get(); - builder.Execute(executeInfo); - swapChain->Present(renderFinishedSemaphores[backTextureIndex].Get()); - swapChainTextureStates[backTextureIndex] = TextureState::present; - frameFence->Wait(); + renderTarget->PrepareForSubmit(builder, backTexture); + renderTarget->Execute(builder, frame); Core::ThreadContext::IncFrameNumber(); BufferPool::Get(*device).Forfeit(); @@ -212,6 +190,7 @@ void BaseTexApp::OnDestroy() device->GetQueue(QueueType::graphics, 0)->Flush(fence.Get()); fence->Wait(); + renderTarget = nullptr; DestroyDeviceResources(*device); }); RenderThread::Get().Flush(); @@ -222,18 +201,12 @@ void BaseTexApp::OnDestroy() void BaseTexApp::CreateDevice() { - device = GetRHIInstance() - ->GetGpu(0) + device = GetGpu() ->RequestDevice( DeviceCreateInfo() .AddQueueRequest(QueueRequestInfo(QueueType::graphics, 1))); } -void BaseTexApp::CreateSurface() -{ - surface = device->CreateSurface(SurfaceCreateInfo(GetPlatformWindow())); -} - void BaseTexApp::CompileAllShaders() const { ShaderCompileOptions options; @@ -252,37 +225,6 @@ void BaseTexApp::FetchShaderInstances() ps = ShaderMap::Get(*device).GetShaderInstance(BaseTexPS::Get(), {}); } -void BaseTexApp::CreateSwapChain() -{ - static std::vector swapChainFormatQualifiers = { - PixelFormat::rgba8Unorm, - PixelFormat::bgra8Unorm - }; - - for (const auto format : swapChainFormatQualifiers) { - if (device->CheckSwapChainFormatSupport(surface.Get(), format, ColorSpace::srgbNonLinear)) { - swapChainFormat = format; - break; - } - } - Assert(swapChainFormat != PixelFormat::max); - - swapChain = device->CreateSwapChain( - SwapChainCreateInfo() - .SetFormat(swapChainFormat) - .SetPresentMode(PresentMode::immediately) - .SetTextureNum(backBufferCount) - .SetWidth(GetWindowWidth()) - .SetHeight(GetWindowHeight()) - .SetSurface(surface.Get()) - .SetPresentQueue(device->GetQueue(QueueType::graphics, 0))); - - for (auto i = 0; i < backBufferCount; i++) { - swapChainTextures[i] = swapChain->GetTexture(i); - swapChainTextureStates[i] = swapChainTextures[i]->GetCreateInfo().initialState; - } -} - void BaseTexApp::CreateVertexAndIndexBuffer() { const std::vector vertices = { @@ -360,7 +302,10 @@ void BaseTexApp::CreateTextureAndSampler() } stbi_image_free(imgData); - sampler = device->CreateSampler(SamplerCreateInfo()); + sampler = device->CreateSampler( + SamplerCreateInfo() + .SetMagFilter(FilterMode::linear) + .SetMinFilter(FilterMode::linear)); // perform buffer->texture copy auto copyCmdBuffer = device->CreateCommandBuffer(QueueType::graphics); @@ -372,7 +317,13 @@ void BaseTexApp::CreateTextureAndSampler() copyRecorder->CopyBufferToTexture( imageBuffer.Get(), texture.Get(), - BufferTextureCopyInfo(0, TextureSubResourceInfo(), UVec3Consts::zero, UVec3(static_cast(width), static_cast(height), 1))); + BufferTextureCopyInfo( + 0, + TextureSubResourceInfo(), + UVec3Consts::zero, + UVec3(static_cast(width), static_cast(height), 1), + copyFootprint.rowPitch, + copyFootprint.slicePitch)); copyRecorder->ResourceBarrier(Barrier::Transition(texture.Get(), TextureState::copyDst, TextureState::shaderReadOnly)); } copyRecorder->EndPass(); @@ -386,15 +337,6 @@ void BaseTexApp::CreateTextureAndSampler() fence->Wait(); } -void BaseTexApp::CreateSyncObjects() -{ - imageReadySemaphore = device->CreateSemaphore(); - for (auto i = 0; i < backBufferCount; i++) { - renderFinishedSemaphores[i] = device->CreateSemaphore(); - } - frameFence = device->CreateFence(true); -} - int main(int argc, char* argv[]) { BaseTexApp application("Rendering-BaseTex"); diff --git a/Sample/Rendering-SSAO/SSAOApplication.cpp b/Sample/Rendering-SSAO/SSAOApplication.cpp index cc3612b2f..dc542f74d 100644 --- a/Sample/Rendering-SSAO/SSAOApplication.cpp +++ b/Sample/Rendering-SSAO/SSAOApplication.cpp @@ -1,8 +1,10 @@ #include #include +#include #include #include #include +#include #include #include #include @@ -141,15 +143,14 @@ class SSAOApp final : public Application { RenderWorkerThreads::Get().Start(); CreateDevice(); - CreateSurface(); + renderTarget = CreateRenderTarget(*device); RenderThread::Get().EmplaceTask([this]() -> void { FetchShaderInstances(); - CreateSwapChain(); + renderTarget->Initialize(); CreateVertexAndIndexBuffer(); CreateQuadBuffer(); CreateSamplers(); - CreateSyncObjects(); PrepareGBuffer(); PrepareSSAOTextures(); PrepareUniformBuffers(); @@ -164,8 +165,7 @@ class SSAOApp final : public Application { uboSceneParams.view = GetCamera().GetViewMatrix(); RenderThread::Get().EmplaceTask([this]() -> void { - frameFence->Reset(); - const auto backTextureIndex = swapChain->AcquireBackTexture(imageReadySemaphore.Get()); + const auto frame = renderTarget->Acquire(); if (uniformBuffers.sceneParams) { auto* uboData = uniformBuffers.sceneParams->Map(MapMode::write, 0, sizeof(UBOSceneParams)); @@ -175,7 +175,7 @@ class SSAOApp final : public Application { RGBuilder builder(*device); - auto* backTexture = builder.ImportTexture(swapChainTextures[backTextureIndex], swapChainTextureStates[backTextureIndex]); + auto* backTexture = builder.ImportTexture(frame.texture, frame.initialState); auto* backTextureView = builder.CreateTextureView(backTexture, RGTextureViewDesc(TextureViewType::colorAttachment, TextureViewDimension::tv2D)); auto* gBufferPos = builder.ImportTexture(gBufferPosTex.Get(), TextureState::shaderReadOnly); @@ -372,7 +372,7 @@ class SSAOApp final : public Application { .AddAttribute(RVertexAttribute(RVertexBinding("TEXCOORD", 0), VertexFormat::float32X2, offsetof(QuadVertex, uv))))) .SetFragmentState( RFragmentState() - .AddColorTarget(ColorTargetState(swapChainFormat, ColorWriteBits::all, false))) + .AddColorTarget(ColorTargetState(renderTarget->GetFormat(), ColorWriteBits::all, false))) .SetPrimitiveState(PrimitiveState(PrimitiveTopologyType::triangle, FillMode::solid, IndexFormat::uint32, FrontFace::ccw, CullMode::none))); auto* compositionBindGroup = builder.AllocateBindGroup( @@ -401,19 +401,12 @@ class SSAOApp final : public Application { recorder.DrawIndexed(6, 1, 0, 0, 0); }, {}, - [backTexture](const RGBuilder& rg, CommandRecorder& recorder) -> void { - recorder.ResourceBarrier(Barrier::Transition(rg.GetRHI(backTexture), TextureState::renderTarget, TextureState::present)); + [this, backTexture](const RGBuilder& rg, CommandRecorder& recorder) -> void { + renderTarget->FinishRenderPass(rg, recorder, backTexture); }); - RGExecuteInfo executeInfo; - executeInfo.semaphoresToWait = { imageReadySemaphore.Get() }; - executeInfo.semaphoresToSignal = { renderFinishedSemaphores[backTextureIndex].Get() }; - executeInfo.inFenceToSignal = frameFence.Get(); - builder.Execute(executeInfo); - - swapChain->Present(renderFinishedSemaphores[backTextureIndex].Get()); - swapChainTextureStates[backTextureIndex] = TextureState::present; - frameFence->Wait(); + renderTarget->PrepareForSubmit(builder, backTexture); + renderTarget->Execute(builder, frame); Core::ThreadContext::IncFrameNumber(); BufferPool::Get(*device).Forfeit(); @@ -432,6 +425,7 @@ class SSAOApp final : public Application { device->GetQueue(QueueType::graphics, 0)->Flush(fence.Get()); fence->Wait(); + renderTarget = nullptr; DestroyDeviceResources(*device); }); RenderThread::Get().Flush(); @@ -443,9 +437,6 @@ class SSAOApp final : public Application { private: static constexpr uint8_t ssaoKernelSize = 64; static constexpr uint8_t ssaoNoiseDim = 16; - static constexpr size_t backBufferCount = 2; - - PixelFormat swapChainFormat = PixelFormat::max; // Shader instances ShaderInstance gBufferVS; @@ -459,10 +450,7 @@ class SSAOApp final : public Application { // Resources UniquePtr device; - UniquePtr surface; - UniquePtr swapChain; - std::array swapChainTextures; - std::array swapChainTextureStates; + UniquePtr renderTarget; UniquePtr vertexBuffer; UniquePtr indexBuffer; @@ -480,10 +468,6 @@ class SSAOApp final : public Application { UniquePtr sampler; UniquePtr noiseSampler; - UniquePtr imageReadySemaphore; - std::array, backBufferCount> renderFinishedSemaphores; - UniquePtr frameFence; - // Uniform buffers struct { UniquePtr sceneParams; @@ -513,6 +497,16 @@ class SSAOApp final : public Application { std::vector materials; // Helper methods + uint32_t GetRandomSeed() const + { + return IsHeadless() ? 0x5a17u : static_cast(std::time(nullptr)); + } + + static float GenerateRandomFloat(std::mt19937& randomEngine) + { + return static_cast(static_cast(randomEngine()) / (static_cast(std::mt19937::max()) + 1.0)); + } + void CompileAllShaders() const { ShaderCompileOptions options; @@ -526,18 +520,12 @@ class SSAOApp final : public Application { void CreateDevice() { - device = GetRHIInstance() - ->GetGpu(0) + device = GetGpu() ->RequestDevice( DeviceCreateInfo() .AddQueueRequest(QueueRequestInfo(QueueType::graphics, 1))); } - void CreateSurface() - { - surface = device->CreateSurface(SurfaceCreateInfo(GetPlatformWindow())); - } - void FetchShaderInstances() { ShaderArtifactRegistry::Get().PerformThreadCopy(); @@ -551,37 +539,6 @@ class SSAOApp final : public Application { compositionPS = ShaderMap::Get(*device).GetShaderInstance(CompositionPS::Get(), {}); } - void CreateSwapChain() - { - static std::vector swapChainFormatQualifiers = { - PixelFormat::rgba8Unorm, - PixelFormat::bgra8Unorm - }; - - for (const auto format : swapChainFormatQualifiers) { - if (device->CheckSwapChainFormatSupport(surface.Get(), format, ColorSpace::srgbNonLinear)) { - swapChainFormat = format; - break; - } - } - Assert(swapChainFormat != PixelFormat::max); - - swapChain = device->CreateSwapChain( - SwapChainCreateInfo() - .SetFormat(swapChainFormat) - .SetPresentMode(PresentMode::immediately) - .SetTextureNum(backBufferCount) - .SetWidth(GetWindowWidth()) - .SetHeight(GetWindowHeight()) - .SetSurface(surface.Get()) - .SetPresentQueue(device->GetQueue(QueueType::graphics, 0))); - - for (auto i = 0; i < backBufferCount; i++) { - swapChainTextures[i] = swapChain->GetTexture(i); - swapChainTextureStates[i] = swapChainTextures[i]->GetCreateInfo().initialState; - } - } - void CreateVertexAndIndexBuffer() { if (!model) return; @@ -663,15 +620,6 @@ class SSAOApp final : public Application { .SetAddressModeV(AddressMode::repeat)); } - void CreateSyncObjects() - { - imageReadySemaphore = device->CreateSemaphore(); - for (auto i = 0; i < backBufferCount; i++) { - renderFinishedSemaphores[i] = device->CreateSemaphore(); - } - frameFence = device->CreateFence(true); - } - void PrepareGBuffer() { // Position buffer (RGBA32F) @@ -800,8 +748,7 @@ class SSAOApp final : public Application { } // SSAO kernel - std::default_random_engine rndEngine(static_cast(time(nullptr))); - std::uniform_real_distribution rndDist(0.0f, 1.0f); + std::mt19937 randomEngine(GetRandomSeed()); std::vector ssaoKernel(ssaoKernelSize); auto lerp = [](float a, float b, float f) ->float { @@ -809,9 +756,9 @@ class SSAOApp final : public Application { }; for (uint32_t i = 0; i < ssaoKernelSize; ++i) { - FVec3 sample(rndDist(rndEngine) * 2.0 - 1.0, rndDist(rndEngine) * 2.0 - 1.0, rndDist(rndEngine)); + FVec3 sample(GenerateRandomFloat(randomEngine) * 2.0 - 1.0, GenerateRandomFloat(randomEngine) * 2.0 - 1.0, GenerateRandomFloat(randomEngine)); sample.Normalize(); - sample *= rndDist(rndEngine); + sample *= GenerateRandomFloat(randomEngine); float scale = static_cast(i) / static_cast(ssaoKernelSize); scale = lerp(0.1f, 1.0f, scale * scale); sample = sample * scale; @@ -834,25 +781,11 @@ class SSAOApp final : public Application { void GenerateNoiseTexture() { - std::default_random_engine rndEngine(static_cast(time(nullptr))); - std::uniform_real_distribution rndDist(0.0f, 1.0f); + std::mt19937 randomEngine(GetRandomSeed()); std::vector ssaoNoise(ssaoNoiseDim * ssaoNoiseDim); for (auto& randomVec : ssaoNoise) { - randomVec = FVec4(rndDist(rndEngine) * 2.0f - 1.0f, rndDist(rndEngine) * 2.0f - 1.0f, 0.0f, 0.0f); - } - - const BufferCreateInfo bufferInfo = BufferCreateInfo() - .SetSize(ssaoNoise.size() * sizeof(FVec4)) - .SetUsages(BufferUsageBits::mapWrite | BufferUsageBits::copySrc) - .SetInitialState(BufferState::staging) - .SetDebugName("noiseStaging"); - - const UniquePtr stagingBuffer = device->CreateBuffer(bufferInfo); - if (stagingBuffer != nullptr) { - auto* data = stagingBuffer->Map(MapMode::write, 0, bufferInfo.size); - memcpy(data, ssaoNoise.data(), bufferInfo.size); - stagingBuffer->Unmap(); + randomVec = FVec4(GenerateRandomFloat(randomEngine) * 2.0f - 1.0f, GenerateRandomFloat(randomEngine) * 2.0f - 1.0f, 0.0f, 0.0f); } noiseTex = device->CreateTexture( @@ -867,6 +800,23 @@ class SSAOApp final : public Application { .SetUsages(TextureUsageBits::copyDst | TextureUsageBits::textureBinding) .SetInitialState(TextureState::undefined)); + const auto copyFootprint = device->GetTextureSubResourceCopyFootprint(*noiseTex, TextureSubResourceInfo()); + const BufferCreateInfo bufferInfo = BufferCreateInfo() + .SetSize(copyFootprint.totalBytes) + .SetUsages(BufferUsageBits::mapWrite | BufferUsageBits::copySrc) + .SetInitialState(BufferState::staging) + .SetDebugName("noiseStaging"); + + const UniquePtr stagingBuffer = device->CreateBuffer(bufferInfo); + if (stagingBuffer != nullptr) { + auto* data = static_cast(stagingBuffer->Map(MapMode::write, 0, bufferInfo.size)); + const auto srcRowPitch = ssaoNoiseDim * sizeof(FVec4); + for (auto y = 0u; y < ssaoNoiseDim; y++) { + memcpy(data + y * copyFootprint.rowPitch, ssaoNoise.data() + y * ssaoNoiseDim, srcRowPitch); + } + stagingBuffer->Unmap(); + } + // Copy data auto copyCmdBuffer = device->CreateCommandBuffer(QueueType::graphics); const UniquePtr commandRecorder = copyCmdBuffer->Begin(); @@ -877,7 +827,13 @@ class SSAOApp final : public Application { copyRecorder->CopyBufferToTexture( stagingBuffer.Get(), noiseTex.Get(), - BufferTextureCopyInfo(0, TextureSubResourceInfo(), UVec3Consts::zero, UVec3(ssaoNoiseDim, ssaoNoiseDim, 1))); + BufferTextureCopyInfo( + 0, + TextureSubResourceInfo(), + UVec3Consts::zero, + UVec3(ssaoNoiseDim, ssaoNoiseDim, 1), + copyFootprint.rowPitch, + copyFootprint.slicePitch)); copyRecorder->ResourceBarrier(Barrier::Transition(noiseTex.Get(), TextureState::copyDst, TextureState::shaderReadOnly)); } copyRecorder->EndPass(); @@ -940,7 +896,13 @@ class SSAOApp final : public Application { copyRecorder->CopyBufferToTexture( stagingBuffer.Get(), diffuseTex.Get(), - BufferTextureCopyInfo(0, TextureSubResourceInfo(), UVec3Consts::zero, UVec3(texData->width, texData->height, 1))); + BufferTextureCopyInfo( + 0, + TextureSubResourceInfo(), + UVec3Consts::zero, + UVec3(texData->width, texData->height, 1), + copyFootprint.rowPitch, + copyFootprint.slicePitch)); copyRecorder->ResourceBarrier(Barrier::Transition(diffuseTex.Get(), TextureState::copyDst, TextureState::shaderReadOnly)); } copyRecorder->EndPass(); diff --git a/Sample/Rendering-SSAO/Shader/Composition.esl b/Sample/Rendering-SSAO/Shader/Composition.esl index 3b093ffbe..6bb784181 100644 --- a/Sample/Rendering-SSAO/Shader/Composition.esl +++ b/Sample/Rendering-SSAO/Shader/Composition.esl @@ -41,20 +41,21 @@ float4 PSMain(VSOutput input) : SV_TARGET float3 normal = normalize(normalTex.Sample(texSampler, input.uv).rgb * 2.0 - 1.0); float4 albedo = albedoTex.Sample(texSampler, input.uv); - float ssao = (ssaoBlur == 1) ? ssaoBluredTex.Sample(texSampler, input.uv).r : ssaoTex.Sample(texSampler, input.uv).r; + float ambientOcclusion = (ssaoBlur == 1) ? ssaoBluredTex.Sample(texSampler, input.uv).r : ssaoTex.Sample(texSampler, input.uv).r; float3 lightposition = float3(0.0, 0.0, 0.0); - float3 L = normalize(lightposition - fragposition); + float3 lightVector = lightposition - fragposition; + float3 L = lightVector * rsqrt(max(dot(lightVector, lightVector), 1e-8f)); float NdotL = max(0.5, dot(normal, L)); float4 outFragColor = float4(1.0f, 1.0f, 1.0f, 1.0f); if (ssaoOnly == 1) { - outFragColor.rgb = ssao.rrr; + outFragColor.rgb = ambientOcclusion.rrr; } else { float3 baseColor = albedo.rgb * NdotL; if (ssao == 1) { - outFragColor.rgb = ssao.rrr; + outFragColor.rgb = ambientOcclusion.rrr; if (ssaoOnly != 1) { outFragColor.rgb *= baseColor; @@ -64,4 +65,4 @@ float4 PSMain(VSOutput input) : SV_TARGET } } return outFragColor; -} \ No newline at end of file +} diff --git a/Sample/Rendering-SSAO/Shader/Gbuffer.esl b/Sample/Rendering-SSAO/Shader/Gbuffer.esl index e60e535c1..c9c0d8ef5 100644 --- a/Sample/Rendering-SSAO/Shader/Gbuffer.esl +++ b/Sample/Rendering-SSAO/Shader/Gbuffer.esl @@ -45,7 +45,6 @@ VSOutput VSMain(VSInput input) #if VULKAN output.position.y = -output.position.y; - output.normal.y = -output.normal.y; #endif output.color = input.color; @@ -72,4 +71,4 @@ FSOutput PSMain(VSOutput input) output.normal = float4(normalize(input.normal) * 0.5 + 0.5, 1.0); output.Albedo = colorTex.Sample(colorSampler, input.uv) * float4(input.color, 1.0); return output; -} \ No newline at end of file +} diff --git a/Sample/Rendering-SSAO/Shader/SSAO.esl b/Sample/Rendering-SSAO/Shader/SSAO.esl index 8fd4ab122..fe72611b4 100644 --- a/Sample/Rendering-SSAO/Shader/SSAO.esl +++ b/Sample/Rendering-SSAO/Shader/SSAO.esl @@ -50,11 +50,15 @@ float PSMain(VSOutput input) : SV_TARGET float3 randomVec = ssaoNoiseTex.Sample(ssaoNoiseSampler, noiseuv).xyz * 2.0 - 1.0; // Create TBN matrix - float3 tangent = normalize(randomVec - normal * dot(randomVec, normal)); + float3 tangentVector = randomVec - normal * dot(randomVec, normal); + float3 tangent = tangentVector * rsqrt(max(dot(tangentVector, tangentVector), 1e-8f)); float3 bitangent = cross(tangent, normal); float3x3 TBN = transpose(float3x3(tangent, bitangent, normal)); // Calculate occlusion value + const float projectionEpsilon = 1e-6f; + const float depthEpsilon = 1e-6f; + const float depthTransition = 1e-3f; float occlusion = 0.0f; for(int i = 0; i < 64; i++) { @@ -64,14 +68,19 @@ float PSMain(VSOutput input) : SV_TARGET // project float4 offset = float4(sampleposition, 1.0f); offset = mul(projection, offset); + if (offset.w <= projectionEpsilon) { + continue; + } offset.xyz /= offset.w; - offset.xyz = offset.xyz * 0.5f + 0.2f; + offset.xy = offset.xy * float2(0.5f, -0.5f) + 0.5f; - float sampleDepth = -posDepthTex.Sample(texSampler, offset.xy).w; + float sampleDepth = posDepthTex.Sample(texSampler, offset.xy).z; - float rangeCheck = smoothstep(0.0f, 1.0f, 0.5 / abs(fragposition.z - sampleDepth)); - occlusion += (sampleDepth >= sampleposition.z ? 1.0f : 0.0f) * rangeCheck; + float depthDelta = sampleDepth - sampleposition.z; + float rangeCheck = smoothstep(0.0f, 1.0f, 0.5f / max(abs(fragposition.z - sampleDepth), depthEpsilon)); + float occlusionWeight = smoothstep(-depthTransition, depthTransition, depthDelta); + occlusion += occlusionWeight * rangeCheck; } occlusion = 1.0 - (occlusion / 64.0); return occlusion; -} \ No newline at end of file +} diff --git a/Sample/Rendering-Triangle/Triangle.cpp b/Sample/Rendering-Triangle/Triangle.cpp index 46fb3b7d7..d5827350e 100644 --- a/Sample/Rendering-Triangle/Triangle.cpp +++ b/Sample/Rendering-Triangle/Triangle.cpp @@ -3,6 +3,7 @@ // #include +#include #include #include #include @@ -57,35 +58,20 @@ class TriangleApplication final : public Application { void OnDestroy() override; private: - static constexpr size_t backBufferCount = 2; - void CreateDevice(); void CompileAllShaders() const; void FetchShaderInstances(); - void CreateSurface(); - void CreateSwapChain(); void CreateTriangleVertexBuffer(); - void CreateSyncObjects(); - PixelFormat swapChainFormat; ShaderInstance triangleVS; ShaderInstance trianglePS; UniquePtr device; - UniquePtr surface; - UniquePtr swapChain; - std::array swapChainTextures; - std::array swapChainTextureStates; + UniquePtr renderTarget; UniquePtr triangleVertexBuffer; - UniquePtr imageReadySemaphore; - std::array, backBufferCount> renderFinishedSemaphores; - UniquePtr frameFence; }; TriangleApplication::TriangleApplication(const std::string& inName) : Application(inName) - , swapChainFormat(PixelFormat::max) - , swapChainTextures() - , swapChainTextureStates() { } @@ -97,24 +83,20 @@ void TriangleApplication::OnCreate() RenderThread::Get().Start(); RenderWorkerThreads::Get().Start(); - // NOTICE: some platform surface need created on main thread, like NSView* in macOS - // so we create device and surface in main thread early CreateDevice(); - CreateSurface(); + renderTarget = CreateRenderTarget(*device); RenderThread::Get().EmplaceTask([this]() -> void { FetchShaderInstances(); - CreateSwapChain(); + renderTarget->Initialize(); CreateTriangleVertexBuffer(); - CreateSyncObjects(); }); } void TriangleApplication::OnDrawFrame() { RenderThread::Get().EmplaceTask([this]() -> void { - frameFence->Reset(); - const auto backTextureIndex = swapChain->AcquireBackTexture(imageReadySemaphore.Get()); + const auto frame = renderTarget->Acquire(); auto* pso = Render::PipelineCache::Get(*device).GetOrCreate( RasterPipelineStateDesc() @@ -127,10 +109,10 @@ void TriangleApplication::OnDrawFrame() .AddAttribute(RVertexAttribute(RVertexBinding("POSITION", 0), VertexFormat::float32X3, offsetof(Vertex, position))))) .SetFragmentState( RFragmentState() - .AddColorTarget(ColorTargetState(swapChainFormat, ColorWriteBits::all, false)))); + .AddColorTarget(ColorTargetState(renderTarget->GetFormat(), ColorWriteBits::all, false)))); RGBuilder builder(*device); - auto* backTexture = builder.ImportTexture(swapChainTextures[backTextureIndex], swapChainTextureStates[backTextureIndex]); + auto* backTexture = builder.ImportTexture(frame.texture, frame.initialState); auto* backTextureView = builder.CreateTextureView(backTexture, RGTextureViewDesc(TextureViewType::colorAttachment, TextureViewDimension::tv2D)); auto* vertexBuffer = builder.ImportBuffer(triangleVertexBuffer.Get(), BufferState::shaderReadOnly); auto* vertexBufferView = builder.CreateBufferView(vertexBuffer, RGBufferViewDesc(BufferViewType::vertex, vertexBuffer->GetDesc().size, 0, VertexBufferViewInfo(sizeof(Vertex)))); @@ -166,18 +148,12 @@ void TriangleApplication::OnDrawFrame() recorder.Draw(3, 1, 0, 0); }, {}, - [backTexture](const RGBuilder& rg, CommandRecorder& recorder) -> void { - recorder.ResourceBarrier(Barrier::Transition(rg.GetRHI(backTexture), TextureState::renderTarget, TextureState::present)); + [this, backTexture](const RGBuilder& rg, CommandRecorder& recorder) -> void { + renderTarget->FinishRenderPass(rg, recorder, backTexture); }); - RGExecuteInfo executeInfo; - executeInfo.semaphoresToWait = { imageReadySemaphore.Get() }; - executeInfo.semaphoresToSignal = { renderFinishedSemaphores[backTextureIndex].Get() }; - executeInfo.inFenceToSignal = frameFence.Get(); - builder.Execute(executeInfo); - swapChain->Present(renderFinishedSemaphores[backTextureIndex].Get()); - swapChainTextureStates[backTextureIndex] = TextureState::present; - frameFence->Wait(); + renderTarget->PrepareForSubmit(builder, backTexture); + renderTarget->Execute(builder, frame); Core::ThreadContext::IncFrameNumber(); BufferPool::Get(*device).Forfeit(); @@ -197,6 +173,7 @@ void TriangleApplication::OnDestroy() device->GetQueue(QueueType::graphics, 0)->Flush(fence.Get()); fence->Wait(); + renderTarget = nullptr; DestroyDeviceResources(*device); }); RenderThread::Get().Flush(); @@ -207,8 +184,7 @@ void TriangleApplication::OnDestroy() void TriangleApplication::CreateDevice() { - device = GetRHIInstance() - ->GetGpu(0) + device = GetGpu() ->RequestDevice( DeviceCreateInfo() .AddQueueRequest(QueueRequestInfo(QueueType::graphics, 1))); @@ -232,42 +208,6 @@ void TriangleApplication::FetchShaderInstances() trianglePS = ShaderMap::Get(*device).GetShaderInstance(TrianglePS::Get(), {}); } -void TriangleApplication::CreateSurface() -{ - surface = device->CreateSurface(SurfaceCreateInfo(GetPlatformWindow())); -} - -void TriangleApplication::CreateSwapChain() -{ - static std::vector swapChainFormatQualifiers = { - PixelFormat::rgba8Unorm, - PixelFormat::bgra8Unorm - }; - - for (const auto format : swapChainFormatQualifiers) { - if (device->CheckSwapChainFormatSupport(surface.Get(), format, ColorSpace::srgbNonLinear)) { - swapChainFormat = format; - break; - } - } - Assert(swapChainFormat != PixelFormat::max); - - swapChain = device->CreateSwapChain( - SwapChainCreateInfo() - .SetFormat(swapChainFormat) - .SetPresentMode(PresentMode::immediately) - .SetTextureNum(backBufferCount) - .SetWidth(GetWindowWidth()) - .SetHeight(GetWindowHeight()) - .SetSurface(surface.Get()) - .SetPresentQueue(device->GetQueue(QueueType::graphics, 0))); - - for (auto i = 0; i < backBufferCount; i++) { - swapChainTextures[i] = swapChain->GetTexture(i); - swapChainTextureStates[i] = swapChainTextures[i]->GetCreateInfo().initialState; - } -} - void TriangleApplication::CreateTriangleVertexBuffer() { const std::vector vertices = { @@ -290,15 +230,6 @@ void TriangleApplication::CreateTriangleVertexBuffer() } } -void TriangleApplication::CreateSyncObjects() -{ - imageReadySemaphore = device->CreateSemaphore(); - for (auto i = 0; i < backBufferCount; i++) { - renderFinishedSemaphores[i] = device->CreateSemaphore(); - } - frameFence = device->CreateFence(true); -} - int main(int argc, char* argv[]) { TriangleApplication application("Rendering-Triangle"); diff --git a/Sample/Test/Baseline/RenderingSample-BaseTexture/baseline.png b/Sample/Test/Baseline/RenderingSample-BaseTexture/baseline.png new file mode 100644 index 000000000..0f2142074 Binary files /dev/null and b/Sample/Test/Baseline/RenderingSample-BaseTexture/baseline.png differ diff --git a/Sample/Test/Baseline/RenderingSample-SSAO/baseline.png b/Sample/Test/Baseline/RenderingSample-SSAO/baseline.png new file mode 100644 index 000000000..b855b2699 Binary files /dev/null and b/Sample/Test/Baseline/RenderingSample-SSAO/baseline.png differ diff --git a/Sample/Test/Baseline/RenderingSample-Triangle/baseline.png b/Sample/Test/Baseline/RenderingSample-Triangle/baseline.png new file mode 100644 index 000000000..ed7e2b129 Binary files /dev/null and b/Sample/Test/Baseline/RenderingSample-Triangle/baseline.png differ diff --git a/Sample/Test/RenderingSampleTest.cpp b/Sample/Test/RenderingSampleTest.cpp new file mode 100644 index 000000000..15c4c339b --- /dev/null +++ b/Sample/Test/RenderingSampleTest.cpp @@ -0,0 +1,138 @@ +#include +#include +#include +#include +#include + +#include +#include + +#define STB_IMAGE_IMPLEMENTATION +#include + +namespace Sample::Test { + struct Params { + std::string samplePath; + std::string rhi; + std::string baselinePath; + std::string outputDirectory; + }; + + static bool ParseParams(const int argc, char* argv[], Params& outParams) + { + if (argc != 9) { + return false; + } + for (int i = 1; i + 1 < argc; i += 2) { + const std::string argument = argv[i]; + const std::string value = argv[i + 1]; + if (argument == "--sample") { + outParams.samplePath = value; + } else if (argument == "--rhi") { + outParams.rhi = value; + } else if (argument == "--baseline") { + outParams.baselinePath = value; + } else if (argument == "--output-dir") { + outParams.outputDirectory = value; + } else { + return false; + } + } + return !outParams.samplePath.empty() && !outParams.rhi.empty() && !outParams.baselinePath.empty() && !outParams.outputDirectory.empty(); + } + + static bool RunSample(const Params& params) + { + const std::filesystem::path outputDirectory(params.outputDirectory); + const auto baselinePath = outputDirectory / "baseline.png"; + const auto actualPath = outputDirectory / "actual.png"; + const auto diffPath = outputDirectory / "diff.bmp"; + std::filesystem::create_directories(outputDirectory); + std::filesystem::remove(actualPath); + std::filesystem::remove(diffPath); + std::filesystem::copy_file(params.baselinePath, baselinePath, std::filesystem::copy_options::overwrite_existing); + + std::vector arguments = {"-headless", "-rhi", params.rhi, "-output", actualPath.string()}; +#if CI + arguments.emplace_back("-softwareGpu"); +#endif + std::cout << "Running: " << params.samplePath; + for (const auto& argument : arguments) { + std::cout << ' ' << argument; + } + std::cout << std::endl; + + const auto exitCode = Common::Process::Run(params.samplePath, arguments); + if (!exitCode.has_value()) { + std::cerr << "Failed to launch rendering sample" << std::endl; + return false; + } + return exitCode.value() == 0; + } + + static cimg_library::CImg LoadImage(const std::string& path) + { + int width = 0; + int height = 0; + int sourceChannels = 0; + constexpr int channels = 4; + unsigned char* pixels = stbi_load(path.c_str(), &width, &height, &sourceChannels, channels); + if (pixels == nullptr) { + throw std::runtime_error("Failed to load image: " + path); + } + + cimg_library::CImg image(width, height, 1, channels); + cimg_forXYC(image, x, y, channel) { + image(x, y, 0, channel) = pixels[(y * width + x) * channels + channel]; + } + stbi_image_free(pixels); + return image; + } + + static bool CompareImages(const Params& params) + { + using Image = cimg_library::CImg; + const auto outputDirectory = std::filesystem::path(params.outputDirectory); + const Image baseline = LoadImage((outputDirectory / "baseline.png").string()); + const Image output = LoadImage((outputDirectory / "actual.png").string()); + if (!baseline.is_sameXYZC(output)) { + std::cerr << "Image dimensions differ: baseline=" << baseline.width() << 'x' << baseline.height() << 'x' << baseline.spectrum() + << ", output=" << output.width() << 'x' << output.height() << 'x' << output.spectrum() << std::endl; + return false; + } + + constexpr double minimumPsnr = 40.0; + const double mse = baseline.MSE(output); + const double psnr = baseline.PSNR(output); + std::cout << "Image comparison: MSE=" << mse << ", PSNR=" << psnr << " dB" << std::endl; + if (psnr >= minimumPsnr) { + return true; + } + + const auto diffPath = outputDirectory / "diff.bmp"; + cimg_library::CImg diff(baseline); + diff -= output; + diff.abs().normalize(0, 255).save_bmp(diffPath.string().c_str()); + std::cerr << "Image comparison failed: PSNR is below " << minimumPsnr << " dB; diff saved to " << diffPath << std::endl; + return false; + } +} + +int main(const int argc, char* argv[]) +{ + try { + Sample::Test::Params params; + if (!Sample::Test::ParseParams(argc, argv, params)) { + std::cerr << "Usage: RenderingSample.Test --sample --rhi --baseline --output-dir " << std::endl; + return 1; + } + if (!Sample::Test::RunSample(params)) { + std::cerr << "Rendering sample failed" << std::endl; + return 1; + } + return Sample::Test::CompareImages(params) ? 0 : 1; + } catch (const std::exception& exception) { + std::cerr << exception.what() << std::endl; + return 1; + } +} diff --git a/ThirdParty/ConanRecipes/swiftshader/conandata.yml b/ThirdParty/ConanRecipes/swiftshader/conandata.yml new file mode 100644 index 000000000..2f0917424 --- /dev/null +++ b/ThirdParty/ConanRecipes/swiftshader/conandata.yml @@ -0,0 +1,7 @@ +platforms: + - Windows-x86_64 + - Macos-armv8 + - Linux-x86_64 +sources: + "2026.8.10-exp": + commit: "6b8d31709ad185dbd64e80865e830a9dbe8e7559" diff --git a/ThirdParty/ConanRecipes/swiftshader/conanfile.py b/ThirdParty/ConanRecipes/swiftshader/conanfile.py new file mode 100644 index 000000000..3a81832d3 --- /dev/null +++ b/ThirdParty/ConanRecipes/swiftshader/conanfile.py @@ -0,0 +1,101 @@ +import os + +from conan import ConanFile +from conan.errors import ConanException, ConanInvalidConfiguration +from conan.tools.build import can_run, check_min_cppstd +from conan.tools.cmake import CMake, CMakeDeps, CMakeToolchain, cmake_layout +from conan.tools.files import copy +from conan.tools.scm import Git + +required_conan_version = ">=2.0.9" + + +class SwiftShaderConan(ConanFile): + name = "swiftshader" + description = "CPU-based Vulkan implementation" + license = "Apache-2.0" + url = "https://github.com/conan-io/conan-center-index" + homepage = "https://github.com/google/swiftshader" + topics = ("vulkan", "driver", "software-renderer") + package_type = "shared-library" + settings = "os", "arch", "compiler", "build_type" + + def layout(self): + cmake_layout(self, src_folder="src") + + def validate(self): + check_min_cppstd(self, 17) + supported_arches = { + "Windows": ("x86_64",), + "Macos": ("armv8", "x86_64"), + "Linux": ("x86_64", "armv8"), + } + if str(self.settings.os) not in supported_arches or str(self.settings.arch) not in supported_arches[str(self.settings.os)]: + raise ConanInvalidConfiguration(f"SwiftShader is not supported on {self.settings.os}/{self.settings.arch}") + + def build_requirements(self): + self.tool_requires("ninja/[>=1.12]") + self.tool_requires("cmake/[>=3.22.1]") + + def source(self): + git = Git(self) + commit = self.conan_data["sources"][self.version]["commit"] + git.run("init .") + git.run("remote add origin https://github.com/google/swiftshader.git") + git.run(f"fetch --depth 1 origin {commit}") + git.run("checkout --detach FETCH_HEAD") + + def generate(self): + toolchain = CMakeToolchain(self, generator="Ninja") + toolchain.cache_variables["SWIFTSHADER_BUILD_TESTS"] = False + toolchain.cache_variables["SWIFTSHADER_BUILD_BENCHMARKS"] = False + toolchain.cache_variables["SWIFTSHADER_BUILD_PVR"] = False + toolchain.cache_variables["SWIFTSHADER_WARNINGS_AS_ERRORS"] = False + if self.settings.os == "Linux": + toolchain.cache_variables["SWIFTSHADER_BUILD_WSI_XCB"] = False + toolchain.cache_variables["SWIFTSHADER_BUILD_WSI_WAYLAND"] = False + toolchain.generate() + + deps = CMakeDeps(self) + deps.generate() + + def build(self): + cmake = CMake(self) + cmake.configure() + cmake.build(target="vk_swiftshader") + + def package(self): + copy(self, "LICENSE.txt", self.source_folder, os.path.join(self.package_folder, "licenses")) + + cmake_system_names = {"Macos": "Darwin", "Windows": "Windows", "Linux": "Linux"} + output_folder = os.path.join(self.build_folder, cmake_system_names[str(self.settings.os)]) + runtime_folder = os.path.join(self.package_folder, "bin" if self.settings.os == "Windows" else "lib") + copy(self, "vk_swiftshader_icd.json", output_folder, runtime_folder) + if self.settings.os == "Windows": + copy(self, "vk_swiftshader.dll", output_folder, runtime_folder) + copy(self, "vk_swiftshader.pdb", output_folder, runtime_folder) + library_name = "vk_swiftshader.dll" + elif self.settings.os == "Macos": + copy(self, "libvk_swiftshader.dylib", output_folder, runtime_folder) + library_name = "libvk_swiftshader.dylib" + else: + copy(self, "libvk_swiftshader.so", output_folder, runtime_folder) + library_name = "libvk_swiftshader.so" + + for required_file in ("vk_swiftshader_icd.json", library_name): + if not os.path.isfile(os.path.join(runtime_folder, required_file)): + raise ConanException(f"Required SwiftShader runtime file was not packaged: {required_file}") + + def package_info(self): + runtime_dir = "bin" if self.settings.os == "Windows" else "lib" + manifest = os.path.join(self.package_folder, runtime_dir, "vk_swiftshader_icd.json") + self.runenv_info.define_path("VK_DRIVER_FILES", manifest) + self.cpp_info.bindirs = ["bin"] if self.settings.os == "Windows" else [] + self.cpp_info.includedirs = [] + self.cpp_info.libdirs = [] + self.cpp_info.libs = [] + + def test(self): + if can_run(self): + bin_path = os.path.join(self.cpp.build.bindirs[0], "test_package") + self.run(bin_path, env="conanrun") diff --git a/ThirdParty/ConanRecipes/swiftshader/test_package/CMakeLists.txt b/ThirdParty/ConanRecipes/swiftshader/test_package/CMakeLists.txt new file mode 100644 index 000000000..1cf5020da --- /dev/null +++ b/ThirdParty/ConanRecipes/swiftshader/test_package/CMakeLists.txt @@ -0,0 +1,9 @@ +cmake_minimum_required(VERSION 3.15) +project(test_package LANGUAGES CXX) + +find_package(VulkanHeaders REQUIRED CONFIG) +find_package(VulkanLoader REQUIRED CONFIG) + +add_executable(${PROJECT_NAME} test_package.cpp) +target_compile_features(${PROJECT_NAME} PRIVATE cxx_std_17) +target_link_libraries(${PROJECT_NAME} PRIVATE Vulkan::Headers Vulkan::Loader) diff --git a/ThirdParty/ConanRecipes/swiftshader/test_package/conanfile.py b/ThirdParty/ConanRecipes/swiftshader/test_package/conanfile.py new file mode 100644 index 000000000..6a31236da --- /dev/null +++ b/ThirdParty/ConanRecipes/swiftshader/test_package/conanfile.py @@ -0,0 +1,28 @@ +import os + +from conan import ConanFile +from conan.tools.build import can_run +from conan.tools.cmake import CMake, cmake_layout + + +class TestPackageConan(ConanFile): + settings = "os", "arch", "compiler", "build_type" + generators = "CMakeDeps", "CMakeToolchain" + + def layout(self): + cmake_layout(self) + + def requirements(self): + self.requires(self.tested_reference_str) + self.requires("vulkan-headers/1.4.350.0") + self.requires("vulkan-loader/1.4.350.0") + + def build(self): + cmake = CMake(self) + cmake.configure() + cmake.build() + + def test(self): + if can_run(self): + bin_path = os.path.join(self.cpp.build.bindir, "test_package") + self.run(bin_path, env="conanrun") diff --git a/ThirdParty/ConanRecipes/swiftshader/test_package/test_package.cpp b/ThirdParty/ConanRecipes/swiftshader/test_package/test_package.cpp new file mode 100644 index 000000000..d52ef113c --- /dev/null +++ b/ThirdParty/ConanRecipes/swiftshader/test_package/test_package.cpp @@ -0,0 +1,44 @@ +#include + +#include +#include + +int main() { + VkApplicationInfo applicationInfo = {}; + applicationInfo.sType = VK_STRUCTURE_TYPE_APPLICATION_INFO; + applicationInfo.pApplicationName = "SwiftShader Conan test package"; + applicationInfo.apiVersion = VK_API_VERSION_1_0; + + VkInstanceCreateInfo instanceCreateInfo = {}; + instanceCreateInfo.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO; + instanceCreateInfo.pApplicationInfo = &applicationInfo; + + VkInstance instance = VK_NULL_HANDLE; + VkResult result = vkCreateInstance(&instanceCreateInfo, nullptr, &instance); + if (result != VK_SUCCESS) { + std::cerr << "vkCreateInstance failed with VkResult " << result << std::endl; + return 1; + } + + uint32_t physicalDeviceCount = 0; + result = vkEnumeratePhysicalDevices(instance, &physicalDeviceCount, nullptr); + if (result != VK_SUCCESS || physicalDeviceCount == 0) { + std::cerr << "SwiftShader exposed no Vulkan physical devices" << std::endl; + vkDestroyInstance(instance, nullptr); + return 1; + } + + std::vector physicalDevices(physicalDeviceCount); + result = vkEnumeratePhysicalDevices(instance, &physicalDeviceCount, physicalDevices.data()); + if (result != VK_SUCCESS) { + std::cerr << "vkEnumeratePhysicalDevices failed with VkResult " << result << std::endl; + vkDestroyInstance(instance, nullptr); + return 1; + } + + VkPhysicalDeviceProperties properties = {}; + vkGetPhysicalDeviceProperties(physicalDevices[0], &properties); + std::cout << "Loaded Vulkan device: " << properties.deviceName << std::endl; + vkDestroyInstance(instance, nullptr); + return 0; +} diff --git a/ThirdParty/Registry.cmake b/ThirdParty/Registry.cmake index 7f2bedcfd..e3283a3a2 100644 --- a/ThirdParty/Registry.cmake +++ b/ThirdParty/Registry.cmake @@ -20,6 +20,7 @@ endif () find_package(glfw3 REQUIRED GLOBAL) find_package(imgui REQUIRED GLOBAL) find_package(stb REQUIRED GLOBAL) +find_package(cimg REQUIRED GLOBAL) find_package(cityhash REQUIRED GLOBAL) find_package(GTest REQUIRED GLOBAL) find_package(benchmark REQUIRED GLOBAL) @@ -35,6 +36,13 @@ find_package(VulkanHeaders REQUIRED GLOBAL) find_package(VulkanLoader REQUIRED GLOBAL) find_package(vulkan-validationlayers REQUIRED GLOBAL) find_package(spirv-cross REQUIRED GLOBAL) +find_package(swiftshader REQUIRED GLOBAL) + +# SwiftShader is a runtime-only package, so Conan's generated targets file does not declare the target named by +# swiftshader_LIBRARIES. Recreate it to carry the runtime files through the existing deployment mechanism. +if (NOT TARGET swiftshader::swiftshader) + add_library(swiftshader::swiftshader INTERFACE IMPORTED GLOBAL) +endif () if (BUILD_BENCHMARK) find_package(EnTT REQUIRED GLOBAL) @@ -54,8 +62,12 @@ if (${CMAKE_SYSTEM_NAME} STREQUAL "Windows") set_target_properties(libclang::libclang PROPERTIES RUNTIME_DEP "${libclang_INCLUDE_DIR}/../bin/libclang.dll") set_target_properties(dxc::dxc PROPERTIES RUNTIME_DEP "${dxc_INCLUDE_DIR}/../bin/dxil.dll;${dxc_INCLUDE_DIR}/../bin/dxcompiler.dll") set_target_properties(vulkan-validationlayers::vulkan-validationlayers PROPERTIES RUNTIME_DEP "${vulkan-validationlayers_PACKAGE_FOLDER_RELEASE}/bin/VkLayer_khronos_validation.dll;${vulkan-validationlayers_PACKAGE_FOLDER_RELEASE}/bin/VkLayer_khronos_validation.json") + set_target_properties(swiftshader::swiftshader PROPERTIES RUNTIME_DEP "${swiftshader_PACKAGE_FOLDER_RELEASE}/bin/vk_swiftshader.dll;${swiftshader_PACKAGE_FOLDER_RELEASE}/bin/vk_swiftshader_icd.json") elseif (${CMAKE_SYSTEM_NAME} STREQUAL "Darwin") find_package(MoltenVK REQUIRED GLOBAL) set_target_properties(vulkan-validationlayers::vulkan-validationlayers PROPERTIES RUNTIME_DEP "${vulkan-validationlayers_PACKAGE_FOLDER_RELEASE}/lib/libVkLayer_khronos_validation.dylib;${vulkan-validationlayers_PACKAGE_FOLDER_RELEASE}/res/vulkan/explicit_layer.d/VkLayer_khronos_validation.json") set_target_properties(molten-vk::molten-vk PROPERTIES RUNTIME_DEP "${MoltenVK_INCLUDE_DIR}/../lib/libMoltenVK.dylib;${MoltenVK_INCLUDE_DIR}/../lib/MoltenVK_icd.json") + set_target_properties(swiftshader::swiftshader PROPERTIES RUNTIME_DEP "${swiftshader_PACKAGE_FOLDER_RELEASE}/lib/libvk_swiftshader.dylib;${swiftshader_PACKAGE_FOLDER_RELEASE}/lib/vk_swiftshader_icd.json") +elseif (${CMAKE_SYSTEM_NAME} STREQUAL "Linux") + set_target_properties(swiftshader::swiftshader PROPERTIES RUNTIME_DEP "${swiftshader_PACKAGE_FOLDER_RELEASE}/lib/libvk_swiftshader.so;${swiftshader_PACKAGE_FOLDER_RELEASE}/lib/vk_swiftshader_icd.json") endif () diff --git a/conanfile.py b/conanfile.py index 078dd7f0f..54317be48 100644 --- a/conanfile.py +++ b/conanfile.py @@ -9,6 +9,7 @@ class ExplosionConan(ConanFile): def requirements(self): self.requires("stb/cci.20230920") + self.requires("cimg/3.3.2") self.requires("cityhash/1.0.1") self.requires("gtest/1.17.0") self.requires("benchmark/1.9.5") @@ -34,5 +35,6 @@ def requirements(self): self.requires("assimp/6.0.2-exp") self.requires("clipp/1.2.3-exp") self.requires("dxc/1.8.2505.1-exp") + self.requires("swiftshader/2026.8.10-exp") if self.settings.os == "Macos": self.requires("molten-vk/1.4.1-exp")