From a3e89b4e8ff5f5a6673d90c1cce0b7ddf42712bb Mon Sep 17 00:00:00 2001 From: Curle <42079760+TheCurle@users.noreply.github.com> Date: Sat, 26 Nov 2022 16:44:16 +0000 Subject: [PATCH] Replace the renderer with a modern, module version. (#7) * New Vulkan Renderer Module & Associated Fixups --- imgui.ini | 41 + projs/shadow/shadow-engine/CMakeLists.txt | 3 + .../shadow-engine/core/inc/core/Module.h | 28 +- .../core/inc/core/ModuleManager.h | 25 +- .../shadow-engine/core/inc/core/SDL2Module.h | 12 +- .../core/inc/core/ShadowApplication.h | 4 +- .../core/inc/core/ShadowWindow.h | 2 +- .../shadow/shadow-engine/core/inc/core/Time.h | 14 +- .../core/inc/debug/DebugModule.h | 13 +- projs/shadow/shadow-engine/core/inc/exports.h | 4 +- .../shadow-engine/core/src/core/App2.txt | 348 + .../core/src/core/ModuleManager.cpp | 38 +- .../core/src/core/SDL2Module.cpp | 16 +- .../core/src/core/ShadowApplication.cpp | 39 +- .../shadow-engine/core/src/core/Time.cpp | 33 +- .../core/src/debug/DebugModule.cpp | 27 +- .../shadow-file-format/src/SFFParser.cpp | 2 +- .../shadow-file-format/test/Catch2Test.cpp | 2 +- .../shadow-reflection/inc/SHObject.h | 2 +- .../shadow-renderer/inc/temp/README.txt | 37 + .../shadow-renderer/inc/temp/model/Builder.h | 137 + .../shadow-renderer/inc/temp/model/Loader.h | 57 + .../shadow-renderer/inc/vlkx/render/Camera.h | 216 +- .../inc/vlkx/render/Geometry.h | 71 +- .../inc/vlkx/render/framebuffer/RenderPass.h | 21 - .../inc/vlkx/render/geometry/SingleRenderer.h | 35 - .../inc/vlkx/render/render_pass/GPUPass.h | 242 + .../render/render_pass/GenericRenderPass.h | 238 + .../render/render_pass/ScreenRenderPass.h | 130 + .../inc/vlkx/render/shader/Descriptor.h | 30 - .../inc/vlkx/render/shader/GeoBuffers.h | 37 - .../inc/vlkx/render/shader/Pipeline.h | 214 +- .../inc/vlkx/render/texture/RenderTexture.h | 46 - .../inc/vlkx/vulkan/CommandBuffer.h | 23 - .../inc/vlkx/vulkan/SwapChain.h | 4 +- .../shadow-renderer/inc/vlkx/vulkan/Tools.h | 205 +- .../inc/vlkx/vulkan/ValidationAndExtension.h | 3 +- .../inc/vlkx/vulkan/VulkanDevice.h | 1 + .../inc/vlkx/vulkan/VulkanManager.h | 95 - .../inc/vlkx/vulkan/VulkanModule.h | 99 + .../inc/vlkx/vulkan/abstraction/Buffer.h | 412 + .../inc/vlkx/vulkan/abstraction/Commands.h | 87 + .../inc/vlkx/vulkan/abstraction/Descriptor.h | 71 + .../inc/vlkx/vulkan/abstraction/Image.h | 331 + .../inc/vlkx/vulkan/abstraction/ImageUsage.h | 297 + .../inc/vlkx/vulkan/abstraction/Queue.h | 8 + .../shadow-renderer/inc/vulkan/vk_mem_alloc.h | 15 - .../shadow-renderer/src/render/Camera.cpp | 139 +- .../shadow-renderer/src/render/Geometry.cpp | 80 +- .../src/render/framebuffer/RenderPass.cpp | 78 - .../src/render/geometry/SingleRenderer.cpp | 85 - .../src/render/pipeline/Pipeline.cpp | 312 + .../src/render/render_pass/GPUPass.cpp | 394 + .../render/render_pass/GenericRenderPass.cpp | 268 + .../render/render_pass/ScreenRenderPass.cpp | 145 + .../src/render/shader/Descriptor.cpp | 137 - .../src/render/shader/GeoBuffers.cpp | 101 - .../src/render/shader/Pipeline.cpp | 200 - .../src/render/texture/RenderTexture.cpp | 66 - .../src/temp/model/Builder.cpp | 238 + .../shadow-renderer/src/temp/model/Loader.cpp | 96 + .../src/vulkan/CommandBuffer.cpp | 60 - .../shadow-renderer/src/vulkan/SwapChain.cpp | 37 +- .../shadow-renderer/src/vulkan/Tools.cpp | 123 + .../src/vulkan/ValidationAndExtension.cpp | 23 +- .../src/vulkan/VulkanDevice.cpp | 3 +- .../src/vulkan/VulkanManager.cpp | 232 - .../src/vulkan/VulkanModule.cpp | 348 + .../src/vulkan/abstraction/Buffer.cpp | 239 + .../src/vulkan/abstraction/Commands.cpp | 177 + .../src/vulkan/abstraction/Descriptor.cpp | 133 + .../src/vulkan/abstraction/Image.cpp | 439 + .../shadow-utility/inc/shadow/util/File.h | 13 + .../inc/shadow/util/RefCounter.h | 121 + .../shadow-utility/inc/stb_image.h | 7897 ++++++++++ .../{src => inc}/string-helpers.h | 0 .../shadow-engine/shadow-utility/src/File.cpp | 26 + .../shadow-utility/src/string-helpers.cpp | 2 +- projs/shadow/shadow-runtime/CMakeLists.txt | 1 - projs/test-game/inc/GameModule.h | 10 +- projs/test-game/src/GameModule.cpp | 197 +- projs/test-game/src/entry.cpp | 13 +- resources/planets/skybox.frag | 10 + resources/planets/skybox.vert | 18 + resources/tri/tri.frag | 13 + resources/tri/tri.frag.spv | Bin 0 -> 764 bytes resources/tri/tri.vert | 11 + resources/tri/tri.vert.spv | Bin 0 -> 1048 bytes resources/walrus/cube.frag | 10 + resources/walrus/cube.frag.spv | Bin 0 -> 628 bytes resources/walrus/cube.vert | 16 + resources/walrus/cube.vert.spv | Bin 0 -> 1392 bytes resources/walrus/texture.png | Bin 0 -> 499093 bytes resources/walrus/walrus.obj | 12745 ++++++++++++++++ vlkx-resources/pkg/01 - Copy.vxp | Bin 0 -> 112864 bytes vlkx-resources/pkg/01.vxp | Bin 0 -> 112866 bytes vlkx-resources/pkg/index.vxi | 1 + vlkx-resources/shader/SPIRV/basic.frag.spv | Bin 0 -> 536 bytes vlkx-resources/shader/SPIRV/basic.vert.spv | Bin 0 -> 1752 bytes .../shader/SPIRV/rt.stage1.frag.spv | Bin 0 -> 28044 bytes .../shader/SPIRV/rt.stage1.vert.spv | Bin 0 -> 2136 bytes .../shader/SPIRV/rt.stage2.frag.spv | Bin 0 -> 24228 bytes .../shader/SPIRV/rt.stage2.vert.spv | Bin 0 -> 1796 bytes .../shader/SPIRV/rt.stage3.frag.spv | Bin 0 -> 23064 bytes .../shader/SPIRV/rt.stage3.vert.spv | Bin 0 -> 2120 bytes vlkx-resources/shader/basic.frag | 9 + vlkx-resources/shader/basic.vert | 20 + vlkx-resources/shader/compileShaders.bat | 5 + .../x64/VFFEdit/vlkx-resources.exe.recipe | 11 + vlkx-resources/x64/VFFEdit/vlkx-resources.log | 7 + .../vlkx-resources.lastbuildstate | 2 + 111 files changed, 27268 insertions(+), 1858 deletions(-) create mode 100644 imgui.ini create mode 100644 projs/shadow/shadow-engine/core/src/core/App2.txt create mode 100644 projs/shadow/shadow-engine/shadow-renderer/inc/temp/README.txt create mode 100644 projs/shadow/shadow-engine/shadow-renderer/inc/temp/model/Builder.h create mode 100644 projs/shadow/shadow-engine/shadow-renderer/inc/temp/model/Loader.h delete mode 100644 projs/shadow/shadow-engine/shadow-renderer/inc/vlkx/render/framebuffer/RenderPass.h delete mode 100644 projs/shadow/shadow-engine/shadow-renderer/inc/vlkx/render/geometry/SingleRenderer.h create mode 100644 projs/shadow/shadow-engine/shadow-renderer/inc/vlkx/render/render_pass/GPUPass.h create mode 100644 projs/shadow/shadow-engine/shadow-renderer/inc/vlkx/render/render_pass/GenericRenderPass.h create mode 100644 projs/shadow/shadow-engine/shadow-renderer/inc/vlkx/render/render_pass/ScreenRenderPass.h delete mode 100644 projs/shadow/shadow-engine/shadow-renderer/inc/vlkx/render/shader/Descriptor.h delete mode 100644 projs/shadow/shadow-engine/shadow-renderer/inc/vlkx/render/shader/GeoBuffers.h delete mode 100644 projs/shadow/shadow-engine/shadow-renderer/inc/vlkx/render/texture/RenderTexture.h delete mode 100644 projs/shadow/shadow-engine/shadow-renderer/inc/vlkx/vulkan/CommandBuffer.h delete mode 100644 projs/shadow/shadow-engine/shadow-renderer/inc/vlkx/vulkan/VulkanManager.h create mode 100644 projs/shadow/shadow-engine/shadow-renderer/inc/vlkx/vulkan/VulkanModule.h create mode 100644 projs/shadow/shadow-engine/shadow-renderer/inc/vlkx/vulkan/abstraction/Buffer.h create mode 100644 projs/shadow/shadow-engine/shadow-renderer/inc/vlkx/vulkan/abstraction/Commands.h create mode 100644 projs/shadow/shadow-engine/shadow-renderer/inc/vlkx/vulkan/abstraction/Descriptor.h create mode 100644 projs/shadow/shadow-engine/shadow-renderer/inc/vlkx/vulkan/abstraction/Image.h create mode 100644 projs/shadow/shadow-engine/shadow-renderer/inc/vlkx/vulkan/abstraction/ImageUsage.h create mode 100644 projs/shadow/shadow-engine/shadow-renderer/inc/vlkx/vulkan/abstraction/Queue.h delete mode 100644 projs/shadow/shadow-engine/shadow-renderer/src/render/framebuffer/RenderPass.cpp delete mode 100644 projs/shadow/shadow-engine/shadow-renderer/src/render/geometry/SingleRenderer.cpp create mode 100644 projs/shadow/shadow-engine/shadow-renderer/src/render/pipeline/Pipeline.cpp create mode 100644 projs/shadow/shadow-engine/shadow-renderer/src/render/render_pass/GPUPass.cpp create mode 100644 projs/shadow/shadow-engine/shadow-renderer/src/render/render_pass/GenericRenderPass.cpp create mode 100644 projs/shadow/shadow-engine/shadow-renderer/src/render/render_pass/ScreenRenderPass.cpp delete mode 100644 projs/shadow/shadow-engine/shadow-renderer/src/render/shader/Descriptor.cpp delete mode 100644 projs/shadow/shadow-engine/shadow-renderer/src/render/shader/GeoBuffers.cpp delete mode 100644 projs/shadow/shadow-engine/shadow-renderer/src/render/shader/Pipeline.cpp delete mode 100644 projs/shadow/shadow-engine/shadow-renderer/src/render/texture/RenderTexture.cpp create mode 100644 projs/shadow/shadow-engine/shadow-renderer/src/temp/model/Builder.cpp create mode 100644 projs/shadow/shadow-engine/shadow-renderer/src/temp/model/Loader.cpp create mode 100644 projs/shadow/shadow-engine/shadow-renderer/src/vulkan/Tools.cpp delete mode 100644 projs/shadow/shadow-engine/shadow-renderer/src/vulkan/VulkanManager.cpp create mode 100644 projs/shadow/shadow-engine/shadow-renderer/src/vulkan/VulkanModule.cpp create mode 100644 projs/shadow/shadow-engine/shadow-renderer/src/vulkan/abstraction/Buffer.cpp create mode 100644 projs/shadow/shadow-engine/shadow-renderer/src/vulkan/abstraction/Commands.cpp create mode 100644 projs/shadow/shadow-engine/shadow-renderer/src/vulkan/abstraction/Descriptor.cpp create mode 100644 projs/shadow/shadow-engine/shadow-renderer/src/vulkan/abstraction/Image.cpp create mode 100644 projs/shadow/shadow-engine/shadow-utility/inc/shadow/util/File.h create mode 100644 projs/shadow/shadow-engine/shadow-utility/inc/shadow/util/RefCounter.h create mode 100644 projs/shadow/shadow-engine/shadow-utility/inc/stb_image.h rename projs/shadow/shadow-engine/shadow-utility/{src => inc}/string-helpers.h (100%) create mode 100644 projs/shadow/shadow-engine/shadow-utility/src/File.cpp create mode 100644 resources/planets/skybox.frag create mode 100644 resources/planets/skybox.vert create mode 100644 resources/tri/tri.frag create mode 100644 resources/tri/tri.frag.spv create mode 100644 resources/tri/tri.vert create mode 100644 resources/tri/tri.vert.spv create mode 100644 resources/walrus/cube.frag create mode 100644 resources/walrus/cube.frag.spv create mode 100644 resources/walrus/cube.vert create mode 100644 resources/walrus/cube.vert.spv create mode 100644 resources/walrus/texture.png create mode 100644 resources/walrus/walrus.obj create mode 100644 vlkx-resources/pkg/01 - Copy.vxp create mode 100644 vlkx-resources/pkg/01.vxp create mode 100644 vlkx-resources/pkg/index.vxi create mode 100644 vlkx-resources/shader/SPIRV/basic.frag.spv create mode 100644 vlkx-resources/shader/SPIRV/basic.vert.spv create mode 100644 vlkx-resources/shader/SPIRV/rt.stage1.frag.spv create mode 100644 vlkx-resources/shader/SPIRV/rt.stage1.vert.spv create mode 100644 vlkx-resources/shader/SPIRV/rt.stage2.frag.spv create mode 100644 vlkx-resources/shader/SPIRV/rt.stage2.vert.spv create mode 100644 vlkx-resources/shader/SPIRV/rt.stage3.frag.spv create mode 100644 vlkx-resources/shader/SPIRV/rt.stage3.vert.spv create mode 100644 vlkx-resources/shader/basic.frag create mode 100644 vlkx-resources/shader/basic.vert create mode 100644 vlkx-resources/shader/compileShaders.bat create mode 100644 vlkx-resources/x64/VFFEdit/vlkx-resources.exe.recipe create mode 100644 vlkx-resources/x64/VFFEdit/vlkx-resources.log create mode 100644 vlkx-resources/x64/VFFEdit/vlkx-resources.tlog/vlkx-resources.lastbuildstate diff --git a/imgui.ini b/imgui.ini new file mode 100644 index 0000000..7162e92 --- /dev/null +++ b/imgui.ini @@ -0,0 +1,41 @@ +[Window][Debug##Default] +Pos=876,8 +Size=315,195 +Collapsed=0 +DockId=0x00000008,0 + +[Window][Dear ImGui Demo] +Pos=1193,178 +Size=62,25 +Collapsed=0 +DockId=0x00000007,0 + +[Window][Game module window] +Pos=1193,8 +Size=62,50 +Collapsed=0 +DockId=0x00000004,0 + +[Window][Time] +Pos=1193,60 +Size=62,49 +Collapsed=0 +DockId=0x00000005,0 + +[Window][Active Modules] +Pos=1193,111 +Size=62,65 +Collapsed=0 +DockId=0x00000006,0 + +[Docking][Data] +DockNode ID=0x00000001 Pos=1196,188 Size=379,195 Split=X + DockNode ID=0x00000008 Parent=0x00000001 SizeRef=652,419 HiddenTabBar=1 Selected=0x55954704 + DockNode ID=0x00000009 Parent=0x00000001 SizeRef=129,419 Split=Y + DockNode ID=0x00000002 Parent=0x00000009 SizeRef=219,34 Split=Y Selected=0xFC1D20C0 + DockNode ID=0x00000004 Parent=0x00000002 SizeRef=219,64 Selected=0xFC1D20C0 + DockNode ID=0x00000005 Parent=0x00000002 SizeRef=219,62 Selected=0xE75A179E + DockNode ID=0x00000003 Parent=0x00000009 SizeRef=219,31 Split=Y Selected=0xEE305C78 + DockNode ID=0x00000006 Parent=0x00000003 SizeRef=219,142 Selected=0xEE305C78 + DockNode ID=0x00000007 Parent=0x00000003 SizeRef=219,55 Selected=0xE87781F4 + diff --git a/projs/shadow/shadow-engine/CMakeLists.txt b/projs/shadow/shadow-engine/CMakeLists.txt index 75f7ae0..e5e2434 100644 --- a/projs/shadow/shadow-engine/CMakeLists.txt +++ b/projs/shadow/shadow-engine/CMakeLists.txt @@ -9,11 +9,13 @@ FILE(GLOB_RECURSE SOURCES core/src/*.cpp shadow-renderer/src/*.cpp shadow-reflection/src/*.cpp + shadow-utility/src/*.cpp ) FILE(GLOB_RECURSE HEADERS core/inc/*.h shadow-renderer/inc/*.h shadow-reflection/inc/*.h + shadow-utility/inc/*.h ) add_library(shadow-engine SHARED ${SOURCES} $) @@ -24,6 +26,7 @@ target_include_directories(shadow-engine core/inc shadow-renderer/inc shadow-reflection/inc + shadow-utility/inc ${glm_SOURCE_DIR} INTERFACE ${imgui_SOURCE_DIR} diff --git a/projs/shadow/shadow-engine/core/inc/core/Module.h b/projs/shadow/shadow-engine/core/inc/core/Module.h index 6baa0c3..1f80379 100644 --- a/projs/shadow/shadow-engine/core/inc/core/Module.h +++ b/projs/shadow/shadow-engine/core/inc/core/Module.h @@ -2,7 +2,8 @@ #define UMBRA_MODULE_H #include "SHObject.h" -#include +#include "SDL_events.h" +#include "vlkx/vulkan/abstraction/Commands.h" namespace ShadowEngine { @@ -29,13 +30,17 @@ namespace ShadowEngine { /// /// update is called each frame /// - virtual void Update() = 0; + virtual void Update(int frame) = 0; + + virtual void Recreate() = 0; virtual void PreRender() = 0; - virtual void Render() = 0; + virtual void Render(VkCommandBuffer& commands, int frame) = 0; - virtual void LateRender() = 0; + virtual void LateRender(VkCommandBuffer& commands, int frame) = 0; + + virtual void OverlayRender() = 0; virtual void AfterFrameEnd() = 0; @@ -52,6 +57,21 @@ namespace ShadowEngine { }; }; + /** + * A class especially for modules that are renderers. + * Allows the engine to access state from the renderer independent of implementation. + */ + class RendererModule : public Module { + public: + // Begin the render pass using the given commands. + // Will call out through the regular modules to gather geometry to render. + virtual void BeginRenderPass(const std::unique_ptr& commands) = 0; + + virtual void EnableEditor() = 0; + + virtual VkExtent2D GetRenderExtent() = 0; + }; + } // ShadowEngine #endif //UMBRA_MODULE_H diff --git a/projs/shadow/shadow-engine/core/inc/core/ModuleManager.h b/projs/shadow/shadow-engine/core/inc/core/ModuleManager.h index 503338e..261b6eb 100644 --- a/projs/shadow/shadow-engine/core/inc/core/ModuleManager.h +++ b/projs/shadow/shadow-engine/core/inc/core/ModuleManager.h @@ -11,22 +11,29 @@ namespace ShadowEngine { public: std::shared_ptr module; std::string domain; + + // Reinterpret this module as if it were a Renderer Module. + // A shortcut for `std::static_pointer_cast>(ShadowEngine::ModuleManager::instance->GetModule("renderer")) + std::shared_ptr operator->() const { return std::static_pointer_cast(module); } }; class ModuleManager { public: - static ModuleManager *instance; + static API ModuleManager *instance; + static ModuleManager* getInstance() { return instance; } std::list modules; + ModuleRef renderer; ModuleManager(); ~ModuleManager(); - void PushModule(std::shared_ptr module, std::string domain); + void PushModule(const std::shared_ptr& module, const std::string& domain); - Module &GetModule(std::string name); + Module &GetModule(const std::string& name); + /* template T *GetModuleByType() { for (auto &module: modules) { @@ -35,15 +42,19 @@ namespace ShadowEngine { } //SH_CORE_ERROR("Can't find the module {0}", T::Type()); return nullptr; - } + } */ void Init(); - void Update(); + void Update(int frame); - void LateRender(); + void LateRender(VkCommandBuffer& commands, int frame); - void Render(); + void OverlayRender(); + + void Recreate(); + + void Render(VkCommandBuffer& commands, int frame); void PreRender(); diff --git a/projs/shadow/shadow-engine/core/inc/core/SDL2Module.h b/projs/shadow/shadow-engine/core/inc/core/SDL2Module.h index 91e784f..50137db 100644 --- a/projs/shadow/shadow-engine/core/inc/core/SDL2Module.h +++ b/projs/shadow/shadow-engine/core/inc/core/SDL2Module.h @@ -8,7 +8,7 @@ #include "Module.h" #include "ShadowWindow.h" -#include +#include "SDL.h" namespace ShadowEngine { @@ -23,11 +23,15 @@ namespace ShadowEngine { void PreInit() override; - void Update() override; + void Update(int frame) override; - void Render() override; + void Recreate() override; - void LateRender() override; + void Render(VkCommandBuffer& commands, int frame) override; + + void OverlayRender() override; + + void LateRender(VkCommandBuffer& commands, int frame) override; std::string GetName() override; diff --git a/projs/shadow/shadow-engine/core/inc/core/ShadowApplication.h b/projs/shadow/shadow-engine/core/inc/core/ShadowApplication.h index b51bf49..3e39cbe 100644 --- a/projs/shadow/shadow-engine/core/inc/core/ShadowApplication.h +++ b/projs/shadow/shadow-engine/core/inc/core/ShadowApplication.h @@ -68,6 +68,6 @@ namespace ShadowEngine { void Init(); void Start(); - - }; + void PollEvents(); + }; } \ No newline at end of file diff --git a/projs/shadow/shadow-engine/core/inc/core/ShadowWindow.h b/projs/shadow/shadow-engine/core/inc/core/ShadowWindow.h index 774738d..295ee49 100644 --- a/projs/shadow/shadow-engine/core/inc/core/ShadowWindow.h +++ b/projs/shadow/shadow-engine/core/inc/core/ShadowWindow.h @@ -1,6 +1,6 @@ #pragma once -#include +#include "SDL.h" namespace ShadowEngine { diff --git a/projs/shadow/shadow-engine/core/inc/core/Time.h b/projs/shadow/shadow-engine/core/inc/core/Time.h index c0fcd66..4f568f7 100644 --- a/projs/shadow/shadow-engine/core/inc/core/Time.h +++ b/projs/shadow/shadow-engine/core/inc/core/Time.h @@ -1,14 +1,20 @@ #pragma once + +#include "exports.h" + class Time { - static int NOW; public: - static int LAST; + static API int NOW; + static API int LAST; - static double deltaTime; - static double deltaTime_ms; + static API double deltaTime; + static API double deltaTime_ms; + + static API double timeSinceStart; + static API double startTime; static void UpdateTime(); }; diff --git a/projs/shadow/shadow-engine/core/inc/debug/DebugModule.h b/projs/shadow/shadow-engine/core/inc/debug/DebugModule.h index 57aa3ed..20f32e6 100644 --- a/projs/shadow/shadow-engine/core/inc/debug/DebugModule.h +++ b/projs/shadow/shadow-engine/core/inc/debug/DebugModule.h @@ -5,8 +5,9 @@ #ifndef UMBRA_DEBUGMODULE_H #define UMBRA_DEBUGMODULE_H -#include +#include "SDL_events.h" #include "core/Module.h" +#include "imgui.h" namespace ShadowEngine::Debug { @@ -17,15 +18,19 @@ namespace ShadowEngine::Debug { bool active; public: - void Render() override; + void Render(VkCommandBuffer& commands, int frame) override {}; void PreInit() override { }; void Init() override { }; - void Update() override { }; + void Recreate() override {}; - void LateRender() override { }; + void OverlayRender() override; + + void Update(int frame) override { }; + + void LateRender(VkCommandBuffer& commands, int frame) override { }; void AfterFrameEnd() override { }; diff --git a/projs/shadow/shadow-engine/core/inc/exports.h b/projs/shadow/shadow-engine/core/inc/exports.h index 97d9502..e46ec7a 100644 --- a/projs/shadow/shadow-engine/core/inc/exports.h +++ b/projs/shadow/shadow-engine/core/inc/exports.h @@ -6,9 +6,9 @@ #if defined(_WIN32) # if defined(EXPORTING_SH_ENGINE) -# define SH_EXPORT __declspec(dllexport) +# define API __declspec(dllexport) # else -# define SH_EXPORT __declspec(dllimport) +# define API __declspec(dllimport) # endif #else // non windows # define SH_EXPORT diff --git a/projs/shadow/shadow-engine/core/src/core/App2.txt b/projs/shadow/shadow-engine/core/src/core/App2.txt new file mode 100644 index 0000000..a5ba809 --- /dev/null +++ b/projs/shadow/shadow-engine/core/src/core/App2.txt @@ -0,0 +1,348 @@ +#include "ShadowApplication.h" + +#define STB_IMAGE_IMPLEMENTATION +#include "stb_image.h" +#define GLM_FORCE_DEPTH_ZERO_TO_ONE +#include "time.h" +#include "imgui.h" +#include "imgui_impl_vulkan.h" +#include "imgui_impl_sdl.h" +#include +#include "vlkx/render/Camera.h" +#include "vlkx/render/render_pass/ScreenRenderPass.h" +#include "spdlog/spdlog.h" +#include "vlkx/vulkan/abstraction/Commands.h" +#include "vlkx/render/Geometry.h" +#include "temp/model/Builder.h" + +#define CATCH(x) \ + try { x } catch (std::exception& e) { spdlog::error(e.what()); exit(0); } + +namespace ShadowEngine { + + struct SkyboxTransform { + alignas(sizeof(glm::mat4)) glm::mat4 value; + }; + + struct PlanetTransform { + alignas(sizeof(glm::mat4)) glm::mat4 model; + alignas(sizeof(glm::mat4)) glm::mat4 projection; + }; + + struct Light { + alignas(sizeof(glm::vec4)) glm::vec4 dirTime; + }; + + std::unique_ptr passManager; + std::unique_ptr renderCommands; + std::unique_ptr camera; + std::unique_ptr light; + + std::unique_ptr skyboxConstant; + std::unique_ptr planetConstant; + std::unique_ptr skyboxModel; + std::unique_ptr planetModel; + std::unique_ptr asteroidModel; + + float aspectRatio; + + ShadowApplication* ShadowApplication::instance = nullptr; + + ShadowApplication::ShadowApplication(int argc, char* argv[]) + { + instance = this; + + if(argc > 1) + { + for (size_t i = 0; i < argc; i++) + { + std::string param(argv[i]); + if(param == "-no-gui") + { + this->no_gui = true; + } + if(param == "-game") + { + this->game = argv[i+1]; + } + } + } + + //game = _setupFunc(); + } + + + ShadowApplication::~ShadowApplication() + { + } + + void ShadowApplication::Init() + { + // Initialize SDL. SDL_Init will return -1 if it fails. + if ( SDL_Init( SDL_INIT_EVERYTHING ) < 0 ) { + //std::cout << "Error initializing SDL: " << SDL_GetError() << std::endl; + //system("pause"); + // End the program + //return 1; + } + + window_ = new ShadowWindow(800,800); + + CATCH(VulkanManager::getInstance()->initVulkan(window_->sdlWindowPtr);) + + renderCommands = std::make_unique(2); + + IMGUI_CHECKVERSION(); + ImGui::CreateContext(); + ImGuiIO& io = ImGui::GetIO(); (void)io; + + // Setup Dear ImGui style + ImGui::StyleColorsDark(); + + VkDescriptorPool imGuiPool; + VulkanManager* vk = VulkanManager::getInstance(); + VkDescriptorPoolSize pool_sizes[] = + { + { VK_DESCRIPTOR_TYPE_SAMPLER, 1000 }, + { VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 1000 }, + { VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, 1000 }, + { VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, 1000 }, + { VK_DESCRIPTOR_TYPE_UNIFORM_TEXEL_BUFFER, 1000 }, + { VK_DESCRIPTOR_TYPE_STORAGE_TEXEL_BUFFER, 1000 }, + { VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 1000 }, + { VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 1000 }, + { VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC, 1000 }, + { VK_DESCRIPTOR_TYPE_STORAGE_BUFFER_DYNAMIC, 1000 }, + { VK_DESCRIPTOR_TYPE_INPUT_ATTACHMENT, 1000 } + }; + + VkDescriptorPoolCreateInfo pool_info = {}; + pool_info.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO; + pool_info.flags = VK_DESCRIPTOR_POOL_CREATE_FREE_DESCRIPTOR_SET_BIT; + pool_info.maxSets = 1000 * IM_ARRAYSIZE(pool_sizes); + pool_info.poolSizeCount = (uint32_t)IM_ARRAYSIZE(pool_sizes); + pool_info.pPoolSizes = pool_sizes; + vkCreateDescriptorPool(vk->getDevice()->logical, &pool_info, VK_NULL_HANDLE, &imGuiPool); + + // Setup Platform/Renderer backends + ImGui_ImplSDL2_InitForVulkan(window_->sdlWindowPtr); + ImGui_ImplVulkan_InitInfo init_info = {}; + init_info.Instance = vk->getVulkan(); + init_info.PhysicalDevice = vk->getDevice()->physical; + init_info.Device = vk->getDevice()->logical; + init_info.QueueFamily = vk->getDevice()->queueData.graphics; + init_info.Queue = vk->getDevice()->graphicsQueue; + init_info.PipelineCache = VK_NULL_HANDLE; + init_info.DescriptorPool = imGuiPool; + init_info.Subpass = 1; + init_info.MinImageCount = vk->getSwapchain()->images.size(); + init_info.ImageCount = vk->getSwapchain()->images.size(); + init_info.MSAASamples = VK_SAMPLE_COUNT_1_BIT; + init_info.Allocator = VK_NULL_HANDLE; + init_info.CheckVkResultFn = nullptr; + + vlkxtemp::ModelBuilder::ShaderPool pool; + + renderCommands = std::make_unique(2); + skyboxConstant = std::make_unique(sizeof(SkyboxTransform), 2); + planetConstant = std::make_unique(sizeof(PlanetTransform), 2); + light = std::make_unique(sizeof(Light), 2); + + aspectRatio = (float) window_->Width / window_->Height; + + vlkx::Camera::Config conf {}; + camera = vlkx::UserPerspectiveCamera::create( {}, {}, { 110, aspectRatio }); + + using vlkxtemp::ModelBuilder; + + + const vlkx::RefCountedTexture::CubemapLocation skybox { + "resources/planets/bg", + { + "left.png", "right.png", + "top.png", "bottom.png", + "front.png", "back.png" + } + }; + + skyboxModel = ModelBuilder { + "Skybox", 2, aspectRatio, + ModelBuilder::SingleMeshModel { "resources/planets/skybox.obj", 1, + { { ModelBuilder::TextureType::Cubemap, { { skybox } } } } + }} + .bindTextures(ModelBuilder::TextureType::Cubemap, 1) + .pushStage(VK_SHADER_STAGE_VERTEX_BIT) + .pushConstant(skyboxConstant.get(), 0) + .shader(VK_SHADER_STAGE_VERTEX_BIT, "resources/planets/skybox.vert.spv") + .shader(VK_SHADER_STAGE_FRAGMENT_BIT, "resources/planets/skybox.frag.spv") + .build(); + + planetModel = ModelBuilder { + "Walrus", 2, aspectRatio, + ModelBuilder::SingleMeshModel {"resources/walrus/walrus.obj", 1, + {{ ModelBuilder::TextureType::Diffuse, { { "resources/walrus/texture.png" } } } } + }} + .bindTextures(ModelBuilder::TextureType::Diffuse, 2) + .uniform(VK_SHADER_STAGE_FRAGMENT_BIT, {{1, 1}}) + .uniformBuffer(1, *light) + .pushStage(VK_SHADER_STAGE_VERTEX_BIT) + .pushConstant(planetConstant.get(), 0) + .shader(VK_SHADER_STAGE_VERTEX_BIT, "resources/walrus/walrus.vert.spv") + .shader(VK_SHADER_STAGE_FRAGMENT_BIT, "resources/walrus/walrus.frag.spv") + .build(); + + passManager = std::make_unique(vlkx::RendererConfig { 2 }); + + passManager->initializeRenderPass(); + + skyboxModel->update(true, VulkanManager::getInstance()->getSwapchain()->extent, VK_SAMPLE_COUNT_1_BIT, *passManager->getPass(), 0); + int cursorX, cursorY; + SDL_GetMouseState(&cursorX, &cursorY); + camera->setPos({ cursorX, cursorY }); + + planetModel->update(true, VulkanManager::getInstance()->getSwapchain()->extent, VK_SAMPLE_COUNT_1_BIT, *passManager->getPass(), 0); + + ImGui_ImplVulkan_Init(&init_info, **passManager->getPass()); + // Upload Fonts + VkTools::immediateExecute([](const VkCommandBuffer& commands) { ImGui_ImplVulkan_CreateFontsTexture(commands); }, VulkanManager::getInstance()->getDevice()); + + SDL_SetRelativeMouseMode(SDL_TRUE); + } + + void printMatrix(glm::mat4 mat) { + for (size_t i = 0; i < 4; i++) { + for (size_t j = 0; j < 4; j++) { + std::cout << mat[i][j] << " "; + } + std::cout << std::endl; + } + + std::cout << std::endl << std::endl; + } + + void updateData(int frame) { + const float elapsed_time = Time::timeSinceStart; + + const glm::vec3 lightDir{glm::sin(elapsed_time * 0.0006f), -0.3f, + glm::cos(elapsed_time * 0.0006f)}; + *light->getData(frame) = + {glm::vec4{lightDir, elapsed_time}}; + light->upload(frame); + + glm::mat4 modelMatrix { 1 }; + modelMatrix = glm::rotate(modelMatrix, elapsed_time * glm::radians(0.0005f), glm::vec3 { 0, 1, 0 }); + const vlkx::Camera& cam = camera->getCamera(); + + const glm::mat4 view = glm::lookAt(glm::vec3{3.0f}, glm::vec3{0.0f}, + glm::vec3{0.0f, 1.0f, 0.0f}); + const glm::mat4 proj = glm::perspective( + glm::radians(45.0f), aspectRatio, + 0.1f, 100.0f); + + glm::mat4 planetProjection = cam.getProjMatrix() * cam.getViewMatrix(); + *planetConstant->getData(frame) = { modelMatrix, planetProjection }; + glm::mat4 skyboxMat = cam.getProjMatrix() * cam.getSkyboxView(); + skyboxConstant->getData(frame)->value = skyboxMat; + } + + void imGuiStartDraw() { + ImGui_ImplVulkan_NewFrame(); + ImGui_ImplSDL2_NewFrame(); + ImGui::NewFrame(); + } + void imGuiEndDraw(const VkCommandBuffer& commands) { + ImGui::Render(); + ImGui_ImplVulkan_RenderDrawData(ImGui::GetDrawData(), commands); + } + + void showDebugWindow(std::unique_ptr* cam) { + #define camPos cam->get()->getCamera().getPosition() + #define camFwd cam->get()->getCamera().getForward() + ImGui::Begin("Camera Debug"); + ImGui::Text("Camera position: (x %f, y %f, z %f)", camPos.x, camPos.y, camPos.z ); + ImGui::Text("Camera target: (x %f, y %f, z %f)", camPos.x + camFwd.x, camPos.y + camFwd.y, camPos.z + camFwd.z); + ImGui::Text("Camera FOV: %f", cam->get()->getCamera().getFieldOfView()); + ImGui::End(); + } + + void ShadowApplication::PollEvents() { + SDL_Event event; + while (SDL_PollEvent(&event)) { // poll until all events are handled! + ImGui_ImplSDL2_ProcessEvent(&event); + + switch(event.type) { + case SDL_KEYDOWN: + switch (event.key.keysym.sym) { + case SDLK_ESCAPE: + camera->reset(); break; + case SDLK_w: + camera->press(vlkx::Camera::Input::Up, Time::deltaTime); break; + case SDLK_s: + camera->press(vlkx::Camera::Input::Down, Time::deltaTime); break; + case SDLK_a: + camera->press(vlkx::Camera::Input::Left, Time::deltaTime); break; + case SDLK_d: + camera->press(vlkx::Camera::Input::Right, Time::deltaTime); break; + } break; + case SDL_MOUSEMOTION: + camera->move(-event.motion.xrel, -event.motion.yrel); break; + + case SDL_MOUSEWHEEL: + camera->scroll(event.wheel.y, 1, 170); break; + case SDL_QUIT: + running = false; break; + } + } + } + + void ShadowApplication::Start() + { + const auto update = [](const int frame) { updateData(frame); }; + + while (running) + { + PollEvents(); + + const auto result = renderCommands->execute(renderCommands->getFrame(), VulkanManager::getInstance()->getSwapchain()->swapChain, update, + [](const VkCommandBuffer& buffer, uint32_t frame) { + passManager->getPass()->execute(buffer, frame, { + // Render our model + [&frame](const VkCommandBuffer& commands) { + planetModel->draw(commands, frame, 1); + skyboxModel->draw(commands, frame, 1); + }, + // Render ImGUI + [&](const VkCommandBuffer& commands) { + imGuiStartDraw(); + + bool showDemo = true; + //if (showDemo) + // ImGui::ShowDemoWindow(&showDemo); + + showDebugWindow(&camera); + + imGuiEndDraw(commands); + } + }); + }); + + if (result.has_value()) + throw std::runtime_error("Resizing is not implemented"); + + renderCommands->nextFrame(); + + Time::UpdateTime(); + + camera->active(true); + } + + vkDeviceWaitIdle(VulkanManager::getInstance()->getDevice()->logical); + + ImGui_ImplVulkan_Shutdown(); + ImGui_ImplSDL2_Shutdown(); + ImGui::DestroyContext(); + + SDL_DestroyWindow(window_->sdlWindowPtr); + SDL_Quit(); + } +} diff --git a/projs/shadow/shadow-engine/core/src/core/ModuleManager.cpp b/projs/shadow/shadow-engine/core/src/core/ModuleManager.cpp index b4b1b4c..e031243 100644 --- a/projs/shadow/shadow-engine/core/src/core/ModuleManager.cpp +++ b/projs/shadow/shadow-engine/core/src/core/ModuleManager.cpp @@ -18,17 +18,18 @@ ShadowEngine::ModuleManager::ModuleManager() } ShadowEngine::ModuleManager::~ModuleManager() -{ -} += default; -void ShadowEngine::ModuleManager::PushModule(std::shared_ptr module, const std::string domain) +void ShadowEngine::ModuleManager::PushModule(const std::shared_ptr& module, const std::string& domain) { ModuleRef r = {module, domain}; modules.emplace_back(r); + if (domain == "renderer") + renderer = r; module->PreInit(); } -ShadowEngine::Module& ShadowEngine::ModuleManager::GetModule(std::string name) +ShadowEngine::Module& ShadowEngine::ModuleManager::GetModule(const std::string& name) { for (auto& module : modules) { @@ -72,30 +73,47 @@ void ShadowEngine::ModuleManager::Event(SDL_Event* evt) } } -void ShadowEngine::ModuleManager::Update() +void ShadowEngine::ModuleManager::Update(int frame) { for (auto& module : modules) { - module.module->Update(); + module.module->Update(frame); } } -void ShadowEngine::ModuleManager::LateRender() +void ShadowEngine::ModuleManager::LateRender(VkCommandBuffer& commands, int frame) { for (auto& module : modules) { - module.module->LateRender(); + module.module->LateRender(commands, frame); } } -void ShadowEngine::ModuleManager::Render() +void ShadowEngine::ModuleManager::Render(VkCommandBuffer& commands, int frame) { for (auto& module : modules) { - module.module->Render(); + module.module->Render(commands, frame); } } +void ShadowEngine::ModuleManager::OverlayRender() +{ + for (auto& module : modules) + { + module.module->OverlayRender(); + } +} + +void ShadowEngine::ModuleManager::Recreate() +{ + for (auto& module : modules) + { + module.module->Recreate(); + } +} + + void ShadowEngine::ModuleManager::AfterFrameEnd() { for (auto& module : modules) diff --git a/projs/shadow/shadow-engine/core/src/core/SDL2Module.cpp b/projs/shadow/shadow-engine/core/src/core/SDL2Module.cpp index 09382dd..e368d2e 100644 --- a/projs/shadow/shadow-engine/core/src/core/SDL2Module.cpp +++ b/projs/shadow/shadow-engine/core/src/core/SDL2Module.cpp @@ -21,20 +21,20 @@ void ShadowEngine::SDL2Module::PreInit() { //return 1; } - _window = new ShadowWindow(800,450); + _window = new ShadowWindow(1280,720); + SDL_SetWindowResizable(_window->sdlWindowPtr, SDL_TRUE); + //SDL_SetRelativeMouseMode(SDL_TRUE); } -void ShadowEngine::SDL2Module::Update() { +void ShadowEngine::SDL2Module::Update(int frame) {} -} +void ShadowEngine::SDL2Module::Recreate() {} -void ShadowEngine::SDL2Module::Render() { +void ShadowEngine::SDL2Module::Render(VkCommandBuffer& commands, int frame) {} -} +void ShadowEngine::SDL2Module::OverlayRender() {} -void ShadowEngine::SDL2Module::LateRender() { - -} +void ShadowEngine::SDL2Module::LateRender(VkCommandBuffer& commands, int frame) {} std::string ShadowEngine::SDL2Module::GetName() { return this->GetType(); diff --git a/projs/shadow/shadow-engine/core/src/core/ShadowApplication.cpp b/projs/shadow/shadow-engine/core/src/core/ShadowApplication.cpp index 2448ecb..4aa26bc 100644 --- a/projs/shadow/shadow-engine/core/src/core/ShadowApplication.cpp +++ b/projs/shadow/shadow-engine/core/src/core/ShadowApplication.cpp @@ -1,17 +1,14 @@ -#include "core/ShadowApplication.h" +#define STB_IMAGE_IMPLEMENTATION +#include "core/ShadowApplication.h" #include "core/Time.h" #include "core/SDL2Module.h" #include "debug/DebugModule.h" #include "dylib.hpp" - - +#include "vlkx/vulkan/abstraction/Commands.h" #include -#include #include -#include -#include -#include +#include #include #define CATCH(x) \ @@ -23,6 +20,8 @@ namespace ShadowEngine { ShadowApplication* ShadowApplication::instance = nullptr; + std::unique_ptr renderCommands; + ShadowApplication::ShadowApplication(int argc, char* argv[]) { instance = this; @@ -70,23 +69,17 @@ namespace ShadowEngine { void ShadowApplication::Init() { - spdlog::info("Starting Shadow Engine!"); + moduleManager.PushModule(std::make_shared(),"core"); + auto renderer = std::make_shared(); + renderer->EnableEditor(); + moduleManager.PushModule(renderer, "renderer"); loadGame(); - printf("exe side: %p \n", VulkanManager::getInstance()); - printf("exe next ID: %llu \n", ShadowEngine::SHObject::GenerateId()); - - moduleManager.PushModule(std::make_shared(),"core"); moduleManager.PushModule(std::make_shared(), "core"); moduleManager.Init(); - - auto sdl2module = moduleManager.GetModuleByType(); - - window_ = sdl2module->_window; - - + renderCommands = std::make_unique(2); } void ShadowApplication::Start() @@ -100,16 +93,14 @@ namespace ShadowEngine { running = false; } - moduleManager.Update(); moduleManager.PreRender(); - VulkanManager::getInstance()->startDraw(); - moduleManager.Render(); - - moduleManager.LateRender(); - VulkanManager::getInstance()->endDraw(); + moduleManager.renderer->BeginRenderPass(renderCommands); moduleManager.AfterFrameEnd(); + + renderCommands->nextFrame(); + Time::UpdateTime(); } moduleManager.Destroy(); diff --git a/projs/shadow/shadow-engine/core/src/core/Time.cpp b/projs/shadow/shadow-engine/core/src/core/Time.cpp index a76e180..650504d 100644 --- a/projs/shadow/shadow-engine/core/src/core/Time.cpp +++ b/projs/shadow/shadow-engine/core/src/core/Time.cpp @@ -1,20 +1,27 @@ #include "core/Time.h" -//#include -//#include +#include -int Time::NOW = 0;//SDL_GetPerformanceCounter(); -int Time::LAST = 0; -double Time::deltaTime_ms = 0; -double Time::deltaTime = 0; +API int Time::NOW = 0;//SDL_GetPerformanceCounter(); +API int Time::LAST = 0; +API double lastFrame = 0; +API double Time::deltaTime_ms = 0; +API double Time::deltaTime = 0; +API double Time::startTime = 0; +API double Time::timeSinceStart = 0; void Time::UpdateTime() { - /* - NOW = SDL_GetTicks(); - deltaTime_ms = LAST > 0 ? (NOW - LAST) *10 : (1.0f / 60.0f); - deltaTime_ms = deltaTime_ms == 0 ? (1.0f / 60.0f) : deltaTime_ms; + using namespace std::chrono; + auto now = system_clock::now(); + auto now_ms = time_point_cast(now); - LAST = NOW; - deltaTime = deltaTime_ms * 0.001; - */ + auto value = now_ms.time_since_epoch(); + double duration = value.count(); + + deltaTime = duration - lastFrame; + if (startTime == 0) + startTime = duration; + timeSinceStart = duration - startTime; + + lastFrame = duration; } diff --git a/projs/shadow/shadow-engine/core/src/debug/DebugModule.cpp b/projs/shadow/shadow-engine/core/src/debug/DebugModule.cpp index 624ab8e..b70de7e 100644 --- a/projs/shadow/shadow-engine/core/src/debug/DebugModule.cpp +++ b/projs/shadow/shadow-engine/core/src/debug/DebugModule.cpp @@ -9,29 +9,26 @@ SHObject_Base_Impl(ShadowEngine::Debug::DebugModule) -void ShadowEngine::Debug::DebugModule::Render() { +void ShadowEngine::Debug::DebugModule::OverlayRender() { - ImGui::Begin("Time", &active, ImGuiWindowFlags_MenuBar); - - ImGui::Text("delta time in ms: %lf", Time::deltaTime_ms); - ImGui::Text("delta time in s: %lf", Time::deltaTime); - ImGui::Text("LAST time in: %ld", Time::LAST); + if (ImGui::Begin("Time", &active, ImGuiWindowFlags_MenuBar)) { + ImGui::Text("delta time in ms: %lf", Time::deltaTime_ms); + ImGui::Text("delta time in s: %lf", Time::deltaTime); + ImGui::Text("LAST time in: %d", Time::LAST); + } ImGui::End(); - ImGui::Begin("Active Modules", &active, ImGuiWindowFlags_MenuBar); + if (ImGui::Begin("Active Modules", &active, ImGuiWindowFlags_MenuBar)) { - ShadowEngine::ModuleManager* m = ShadowEngine::ModuleManager::instance; + ShadowEngine::ModuleManager *m = ShadowEngine::ModuleManager::instance; - - - ImGui::TextColored(ImVec4(1.0f, 1.0f, 0.4f, 1.0f), "Active Modules:"); - for (auto& module : m->modules) - { - ImGui::Text("%s", module.module->GetName().c_str()); + ImGui::TextColored(ImVec4(1.0f, 1.0f, 0.4f, 1.0f), "Active Modules:"); + for (auto &module: m->modules) { + ImGui::Text("%s", module.module->GetName().c_str()); + } } - ImGui::End(); } diff --git a/projs/shadow/shadow-engine/shadow-file-format/src/SFFParser.cpp b/projs/shadow/shadow-engine/shadow-file-format/src/SFFParser.cpp index ba5a3fe..67ce66a 100644 --- a/projs/shadow/shadow-engine/shadow-file-format/src/SFFParser.cpp +++ b/projs/shadow/shadow-engine/shadow-file-format/src/SFFParser.cpp @@ -1,5 +1,5 @@ #include "SFFParser.h" -#include "../../shadow-utility/src/string-helpers.h" +#include "string-helpers.h" #include diff --git a/projs/shadow/shadow-engine/shadow-file-format/test/Catch2Test.cpp b/projs/shadow/shadow-engine/shadow-file-format/test/Catch2Test.cpp index 6592347..11727ed 100644 --- a/projs/shadow/shadow-engine/shadow-file-format/test/Catch2Test.cpp +++ b/projs/shadow/shadow-engine/shadow-file-format/test/Catch2Test.cpp @@ -1,5 +1,5 @@ #define CATCH_CONFIG_MAIN -#include +#include "catch2/catch.hpp" TEST_CASE("15 is less than 20", "[numbers]") { REQUIRE(15 < 20); diff --git a/projs/shadow/shadow-engine/shadow-reflection/inc/SHObject.h b/projs/shadow/shadow-engine/shadow-reflection/inc/SHObject.h index c61f30e..7471761 100644 --- a/projs/shadow/shadow-engine/shadow-reflection/inc/SHObject.h +++ b/projs/shadow/shadow-engine/shadow-reflection/inc/SHObject.h @@ -25,7 +25,7 @@ namespace ShadowEngine { * \brief Generates a new UID for each call * \return the next Unique ID that was just generated */ - SH_EXPORT static uint64_t GenerateId() noexcept; + API static uint64_t GenerateId() noexcept; public: /** diff --git a/projs/shadow/shadow-engine/shadow-renderer/inc/temp/README.txt b/projs/shadow/shadow-engine/shadow-renderer/inc/temp/README.txt new file mode 100644 index 0000000..568348a --- /dev/null +++ b/projs/shadow/shadow-engine/shadow-renderer/inc/temp/README.txt @@ -0,0 +1,37 @@ + +===== + +This folder exists to store VLKX renderer objects that are TEMPORARY. +It currently contains: + +===== + +Model Loader system + +The Model Loader is temporarily implemented as a raw file reader and OBJ parser. +It should be removed when the File Format system is able to parse model and texture files. + +==== + +Model Abstraction system + +The Model Abstraction allows you to create a model with: + - A mesh + - An arbitrary number of textures + - A push constant + - An arbitrarily large uniform buffer + - A fragment and vertex shader + +In all, it contains a custom Render Pipeline that will be used to draw the model. +This allows for drastic and visually appealing effects. + +It should be maintained and moved into the appropriate Shadow module once ready. + +==== + +Model Builder system + +The model Builder allows for simple construction of Models using the Model Abstraction system above. +It consumes the Model Loader and returns a Model Abstraction. + +It should be MOVED into the File Format parsing system once it is ready. \ No newline at end of file diff --git a/projs/shadow/shadow-engine/shadow-renderer/inc/temp/model/Builder.h b/projs/shadow/shadow-engine/shadow-renderer/inc/temp/model/Builder.h new file mode 100644 index 0000000..a82c9c0 --- /dev/null +++ b/projs/shadow/shadow-engine/shadow-renderer/inc/temp/model/Builder.h @@ -0,0 +1,137 @@ +#pragma once + +#include "vlkx/render/shader/Pipeline.h" +#include "Loader.h" +#include "vlkx/render/render_pass/GenericRenderPass.h" +#include "vlkx/vulkan/abstraction/Descriptor.h" + +namespace vlkxtemp { + + class Model; + + class ModelBuilder { + public: + + using ShaderPool = vlkx::ShaderModule::ReleasePool; + using TextureType = ModelLoader::TextureType; + using TexturePerMesh = std::array>, static_cast(TextureType::Count)>; + using BindingPoints = std::map; + using TextureSource = vlkx::RefCountedTexture::ImageLocation; + using TextureSources = std::map>; + + class ModelResource { + public: + virtual ~ModelResource() = default; + virtual void load(ModelBuilder* builder) const = 0; + }; + + class SingleMeshModel : public ModelResource { + public: + SingleMeshModel(std::string&& path, int indexBase, TextureSources&& sources) + : objFile(std::move(path)), objIndexBase(indexBase), textureSources(std::move(sources)) {} + + void load(ModelBuilder* builder) const override; + private: + const std::string objFile; + const int objIndexBase; + const TextureSources textureSources; + }; + + class MultiMeshModel : public ModelResource { + public: + MultiMeshModel(std::string&& modelDir, std::string&& textureDir) + : models(std::move(modelDir)), textures(std::move(textureDir)) {} + + void load(ModelBuilder* builder) const override; + + private: + const std::string models; + const std::string textures; + }; + + struct ModelPushConstant { + struct Meta { + const vlkx::PushConstant* constants; + uint32_t offset; + }; + + VkShaderStageFlags stage; + std::vector constants; + }; + + using Descriptors = std::vector>; + + ModelBuilder(std::string&& name, int frames, float aspect, const ModelResource& resource); + + ModelBuilder(const ModelBuilder&) = delete; + ModelBuilder& operator=(const ModelBuilder&) = delete; + + ModelBuilder& texture(TextureType type, const TextureSource& source); + ModelBuilder& bindTextures(TextureType type, uint32_t point); + ModelBuilder& instanceBuffer(vlkx::PerInstanceVertexBuffer* buffer); + ModelBuilder& uniform(VkShaderStageFlags stage, std::vector&& bindings); + ModelBuilder& uniformBuffer(uint32_t point, const vlkx::UniformBuffer& buffer); + ModelBuilder& pushStage(VkShaderStageFlags stage); + ModelBuilder& pushConstant(const vlkx::PushConstant* constant, uint32_t offset); + ModelBuilder& shader(VkShaderStageFlagBits stage, std::string&& file); + + std::unique_ptr build(); + + private: + std::vector createDescs() const; + + const int frames; + const float aspectRatio; + + std::unique_ptr vertexBuffer; + std::vector textures; + TexturePerMesh sharedTextures; + BindingPoints bindPoints; + + std::vector instanceBuffers; + std::vector uniformMeta; + std::vector uniformBufferMeta; + + std::optional pushConstants; + std::unique_ptr pipelineBuilder; + }; + + class Model { + public: + + Model(const Model&) = delete; + Model& operator=(const Model&) = delete; + + void update(bool opaque, const VkExtent2D& frame, VkSampleCountFlagBits samples, const vlkx::RenderPass& pass, uint32_t subpass, bool flipY = true); + void draw(const VkCommandBuffer& commands, int frame, uint32_t instances) const; + + private: + friend std::unique_ptr ModelBuilder::build(); + using Descriptors = ModelBuilder::Descriptors; + using ModelPushConstant = ModelBuilder::ModelPushConstant; + using TexturePerMesh = ModelBuilder::TexturePerMesh; + + Model(float aspectRatio, + std::unique_ptr&& vertexBuffer, + std::vector&& perInstanceBuffers, + std::optional&& pushConstants, + TexturePerMesh&& sharedTextures, + std::vector&& textures, + std::vector&& descriptors, + std::unique_ptr&& pipelineBuilder) + : aspectRatio(aspectRatio), vertexBuffer(std::move(vertexBuffer)), perInstanceBuffers(std::move(perInstanceBuffers)), + pushConstants(std::move(pushConstants)), sharedTextures(std::move(sharedTextures)), textures(std::move(textures)), + descriptors(std::move(descriptors)), pipelineBuilder(std::move(pipelineBuilder)) {} + + const float aspectRatio; + const std::unique_ptr vertexBuffer; + const std::vector perInstanceBuffers; + const std::optional pushConstants; + const TexturePerMesh sharedTextures; + const std::vector textures; + const std::vector descriptors; + + std::unique_ptr pipelineBuilder; + std::unique_ptr pipeline; + }; +} \ No newline at end of file diff --git a/projs/shadow/shadow-engine/shadow-renderer/inc/temp/model/Loader.h b/projs/shadow/shadow-engine/shadow-renderer/inc/temp/model/Loader.h new file mode 100644 index 0000000..9cbe90f --- /dev/null +++ b/projs/shadow/shadow-engine/shadow-renderer/inc/temp/model/Loader.h @@ -0,0 +1,57 @@ +#pragma once +#include +#include +#include "vlkx/render/Geometry.h" + +namespace vlkxtemp { + + struct Wavefront { + Wavefront(std::string_view path, size_t base); + Wavefront(const Wavefront&) = delete; + + Wavefront& operator=(const Wavefront&) = delete; + + std::vector indices; + std::vector vertices; + }; + + class ModelLoader { + public: + enum class TextureType { + Diffuse, + Specular, + Reflection, + Cubemap, + Count + }; + + struct TextureData { + TextureData(TextureData&&) noexcept = default; + TextureData& operator=(TextureData&&) noexcept = default; + + std::string path; + TextureType type; + }; + + struct MeshData { + MeshData() = default; + MeshData(MeshData&&) noexcept = default; + MeshData& operator=(MeshData&&) noexcept = default; + + std::vector vertices; + std::vector indices; + std::vector textures; + }; + + ModelLoader(const std::string& model, const std::string& textures); + + ModelLoader(const ModelLoader&) = delete; + ModelLoader& operator=(const ModelLoader&) = delete; + + const std::vector& getMeshes() const { return meshes; } + + private: + + std::vector meshes; + }; +} \ No newline at end of file diff --git a/projs/shadow/shadow-engine/shadow-renderer/inc/vlkx/render/Camera.h b/projs/shadow/shadow-engine/shadow-renderer/inc/vlkx/render/Camera.h index 5511f72..9684718 100644 --- a/projs/shadow/shadow-engine/shadow-renderer/inc/vlkx/render/Camera.h +++ b/projs/shadow/shadow-engine/shadow-renderer/inc/vlkx/render/Camera.h @@ -1,23 +1,213 @@ #pragma once +#include #define GLM_FORCE_RADIAN #include #include +#include -class Camera { -public: +namespace vlkx { + class Camera { + public: - // Set up the camera. - void init(float fov, float width, float height, float near, float far); + enum class Input { + Up, Down, Left, Right + }; + struct Movement { + float moveSpeed = 10; + float turnSpeed = 0.0005f; + std::optional center; + }; - // Move the camera. - void setPosition(glm::vec3 positionIn); + /** + * Camera configuration; with defaults. + * Left and forward vectors are calculated from up, pos and target. + */ + struct Config { + float nearPlane = 0.1f; // The nearest a vertex can be to the camera before being clipped + float farPlane = 100; // The furthest a vertex can be from the camera before clipped + glm::vec3 upV{0, 1, 0}; // The vector pointing straight up from the camera + glm::vec3 pos{0, 0, 0}; // The position of the camera in the world + glm::vec3 target{1, 0, 0}; // The point the camera is looking at + }; - glm::mat4 getViewMatrix() { return viewMatrix; }; - glm::mat4 getProjectionMatrix() { return projectionMatrix; } + Camera(const Camera &) = delete; -private: - glm::mat4 projectionMatrix; - glm::mat4 viewMatrix; - glm::vec3 position; -}; \ No newline at end of file + Camera &operator=(const Camera &) = delete; + + virtual ~Camera() = default; + + Camera &move(const glm::vec3 &delta); + + Camera &setPos(const glm::vec3 &pos); + + Camera &up(const glm::vec3 &up); + + Camera &forward(const glm::vec3 &forward); + + glm::mat4 getViewMatrix() const; + + glm::mat4 getSkyboxView() const { + return glm::mat4{glm::mat3{getViewMatrix()}}; + } + + virtual glm::mat4 getProjMatrix() const = 0; + + const glm::vec3& getPosition() const { return position; } + const glm::vec3& getUp() const { return upVector; } + const glm::vec3& getForward() const { return frontVector; } + const glm::vec3& getRight() const { return rightVector; } + + protected: + explicit Camera(const Config &conf) : nearPlane(conf.nearPlane), farPlane(conf.farPlane), position(conf.pos), + upVector(glm::normalize(conf.upV)) { + forward(conf.target - position); + } + + const float nearPlane; + const float farPlane; + private: + + glm::vec3 position; + glm::vec3 upVector; + glm::vec3 frontVector; + glm::vec3 rightVector; + + }; + + class PerspectiveCamera : public Camera { + public: + + struct Frustum { + float fov; + float aspect; + }; + + struct RT { + glm::vec3 up; + glm::vec3 forward; + glm::vec3 right; + }; + + PerspectiveCamera(const Camera::Config &conf, const Frustum &frus) : + Camera(conf), aspectRatio(frus.aspect), fov(frus.fov) {} + + PerspectiveCamera(const PerspectiveCamera &) = delete; + + PerspectiveCamera &operator=(const PerspectiveCamera &) = delete; + + PerspectiveCamera &fieldOfView(float newFov); + + RT getRT() const; + + glm::mat4 getProjMatrix() const override; + + float getFieldOfView() const { return fov; } + + float getAspect() const { return aspectRatio; } + + private: + + const float aspectRatio; + float fov; + }; + + class OrthographicCamera : public Camera { + public: + struct OrthoConfig { + float width; + float aspect; + }; + + static OrthoConfig getFullscreenConfig() { + return {2, 1}; + } + + OrthographicCamera(const Camera::Config &config, const OrthoConfig &ortho) + : Camera(config), aspectRatio(ortho.aspect), width(ortho.width) {} + + OrthographicCamera(const OrthographicCamera &) = delete; + + OrthographicCamera &operator=(const OrthographicCamera &) = delete; + + OrthographicCamera &setWidth(float width); + + glm::mat4 getProjMatrix() const override; + + float getWidth() const { return width; } + + private: + + const float aspectRatio; + float width; + }; + + template + class UserCamera { + public: + UserCamera(const UserCamera &) = delete; + + UserCamera &operator=(const UserCamera &) = delete; + + virtual ~UserCamera() = default; + + void setInternal(std::function op); + + void setPos(const glm::dvec2 &pos) { cursorPos = pos; } + + void move(double x, double y); + + bool scroll(double delta, double min, double max); + + void press(Camera::Input key, float time); + + void active(bool active) { isActive = active; } + + const Type &getCamera() const { return *camera; } + + UserCamera(const Camera::Movement &movement, std::unique_ptr &&cam) + : config(movement), camera(std::move(cam)) { + reset(); + } + + void reset(); + + private: + + + const Camera::Movement config; + bool isActive = false; + + std::unique_ptr camera; + glm::dvec2 cursorPos; + glm::vec3 refForward; + glm::vec3 refLeft; + + float pitch; + float yaw; + }; + + class UserPerspectiveCamera : public UserCamera { + public: + static std::unique_ptr + create(const Camera::Movement &movement, const Camera::Config &config, + const PerspectiveCamera::Frustum &frustum) { + return std::make_unique(movement, + std::make_unique(config, frustum)); + } + + protected: + using UserCamera::UserCamera; + }; + + class UserOrthoCamera : public UserCamera { + public: + static std::unique_ptr create(const Camera::Movement &movement, const Camera::Config &config, + const OrthographicCamera::OrthoConfig &ortho) { + return std::make_unique(movement, std::make_unique(config, ortho)); + } + + protected: + using UserCamera::UserCamera; + }; +} \ No newline at end of file diff --git a/projs/shadow/shadow-engine/shadow-renderer/inc/vlkx/render/Geometry.h b/projs/shadow/shadow-engine/shadow-renderer/inc/vlkx/render/Geometry.h index cce60c1..d208974 100644 --- a/projs/shadow/shadow-engine/shadow-renderer/inc/vlkx/render/Geometry.h +++ b/projs/shadow/shadow-engine/shadow-renderer/inc/vlkx/render/Geometry.h @@ -28,65 +28,66 @@ namespace Geo { }; // All of the metadata involved with a vertex. - struct Vertex { + struct VertexAll { glm::vec3 position; // XYZ coordinates of the vertex's position. glm::vec3 normal; // Unit vector pointing away from the outer surface of the vertex. - glm::vec3 color; // The color of the vertex. glm::vec2 texture; // The u/v coordinates of this vertex in the bound texture. // How fast should vertex data be read from RAM? static VkVertexInputBindingDescription getBindingDesc() { VkVertexInputBindingDescription desc = {}; desc.binding = 0; - desc.stride = sizeof(Vertex); + desc.stride = sizeof(VertexAll); desc.inputRate = VK_VERTEX_INPUT_RATE_VERTEX; return desc; } // How should vertexes be handled? - static std::array getAttributeDesc() { - std::array descs = {}; - - // Attribute 0; position. Location 0, 3x 32-bit float. - descs[0].binding = 0; - descs[0].location = 0; - descs[0].format = VK_FORMAT_R32G32B32_SFLOAT; - descs[0].offset = offsetof(Vertex, position); - - // Attribute 1; normal. Location 1, 3x 32-bit float. - descs[1].binding = 0; - descs[1].location = 1; - descs[1].format = VK_FORMAT_R32G32B32_SFLOAT; - descs[1].offset = offsetof(Vertex, normal); - - // Attribute 2; color. Location 2, 3x 32-bit float. - descs[2].binding = 0; - descs[2].location = 2; - descs[2].format = VK_FORMAT_R32G32B32_SFLOAT; - descs[2].offset = offsetof(Vertex, color); - - // Attribute 3; texture. Location 3, 2x 32-bit float. - descs[3].binding = 0; - descs[3].location = 3; - descs[3].format = VK_FORMAT_R32G32_SFLOAT; - descs[3].offset = offsetof(Vertex, texture); - - return descs; + static std::vector getAttributeDesc() { + return { + { 0, 0, VK_FORMAT_R32G32B32_SFLOAT, static_cast(offsetof(VertexAll, position)) }, + { 0, 1, VK_FORMAT_R32G32B32_SFLOAT, static_cast(offsetof(VertexAll, normal)) }, + { 0, 2, VK_FORMAT_R32G32_SFLOAT, static_cast(offsetof(VertexAll, texture)) } + }; } }; + // All of the metadata involved with a vertex. + struct VertexColor { + glm::vec3 position; // XYZ coordinates of the vertex's position. + glm::vec3 color; // The color of the vertex. + + // How fast should vertex data be read from RAM? + static VkVertexInputBindingDescription getBindingDesc() { + VkVertexInputBindingDescription desc = {}; + desc.binding = 0; + desc.stride = sizeof(VertexColor); + desc.inputRate = VK_VERTEX_INPUT_RATE_VERTEX; + + return desc; + } + + // How should vertexes be handled? + static std::vector getAttributeDesc() { + return { + { 0, 0, VK_FORMAT_R32G32B32_SFLOAT, static_cast(offsetof(VertexColor, position)) }, + { 0, 1, VK_FORMAT_R32G32B32_SFLOAT, static_cast(offsetof(VertexColor, color)) } + }; + } + }; + // Contains data about a given Mesh. class Mesh { public: // Pre-load the data for a triangle into the given buffers. - static void setTriData(std::vector& vertices, std::vector& indices); + static void setTriData(std::vector& vertices, std::vector& indices); // Pre-load the data for a quad into the given buffers. - static void setQuadData(std::vector& vertices, std::vector& indices); + static void setQuadData(std::vector& vertices, std::vector& indices); // Pre-load the data for a cube into the given buffers. - static void setCubeData(std::vector& vertices, std::vector& indices); + static void setCubeData(std::vector& vertices, std::vector& indices); // Pre-load the data for a sphere into the given buffers. - static void setSphereData(std::vector& vertices, std::vector& indices); + static void setSphereData(std::vector& vertices, std::vector& indices); }; diff --git a/projs/shadow/shadow-engine/shadow-renderer/inc/vlkx/render/framebuffer/RenderPass.h b/projs/shadow/shadow-engine/shadow-renderer/inc/vlkx/render/framebuffer/RenderPass.h deleted file mode 100644 index 129bc39..0000000 --- a/projs/shadow/shadow-engine/shadow-renderer/inc/vlkx/render/framebuffer/RenderPass.h +++ /dev/null @@ -1,21 +0,0 @@ -#pragma once - -#include -#include - -class RenderPass { -public: - RenderPass(); - ~RenderPass(); - - VkRenderPass pass; - - void createVertexRenderPass(VkFormat format); - void createRTRenderPass(VkFormat format); - void createRTPhysicsPass(VkFormat format); - - void beginRenderPass(std::vector clearValues, VkCommandBuffer commands, VkFramebuffer framebuffer, VkExtent2D extent); - void endRenderPass(VkCommandBuffer commands); - - void destroy(); -}; \ No newline at end of file diff --git a/projs/shadow/shadow-engine/shadow-renderer/inc/vlkx/render/geometry/SingleRenderer.h b/projs/shadow/shadow-engine/shadow-renderer/inc/vlkx/render/geometry/SingleRenderer.h deleted file mode 100644 index d867548..0000000 --- a/projs/shadow/shadow-engine/shadow-renderer/inc/vlkx/render/geometry/SingleRenderer.h +++ /dev/null @@ -1,35 +0,0 @@ -#pragma once - -#include -#include -#include -#include - -// Renders a single object. -class SingleRenderer { -public: - - void createSingleRenderer(Geo::MeshType type, glm::vec3 posIn, glm::vec3 scaleIn); - - void updateUniforms(Camera camera); - - void draw(); - - void destroy(); - - void setPosition(glm::vec3 newPos) { position = newPos; } - void setRotation(glm::mat4 newRot) { rotation = newRot; } - - Descriptor getDescriptor() { return descriptor; } - - glm::mat4 getRotation() { return rotation; } -private: - - Pipeline pipeline; - GeoBuffers buffers; - Descriptor descriptor; - - glm::vec3 position; - glm::vec3 scale; - glm::mat4 rotation; -}; \ No newline at end of file diff --git a/projs/shadow/shadow-engine/shadow-renderer/inc/vlkx/render/render_pass/GPUPass.h b/projs/shadow/shadow-engine/shadow-renderer/inc/vlkx/render/render_pass/GPUPass.h new file mode 100644 index 0000000..87ea9f5 --- /dev/null +++ b/projs/shadow/shadow-engine/shadow-renderer/inc/vlkx/render/render_pass/GPUPass.h @@ -0,0 +1,242 @@ +#pragma once + +#include +#include "vlkx/vulkan/abstraction/ImageUsage.h" +#include +#include +#include +#include "GenericRenderPass.h" + +namespace vlkx { + + /** + * The common base class for rendering and computing passes that run on the GPU. + * Provides some utility methods for handling attachment metadata between subpasses. + */ + class CommonPass { + public: + + explicit CommonPass(int passes) : numPasses(passes) {} + + // Delete the copy and move constructors + CommonPass(const CommonPass&) = delete; + CommonPass& operator=(const CommonPass&) = delete; + virtual ~CommonPass() = default; + + // Get the image layout of the given image at the start of this pass + VkImageLayout getInitialLayout(const std::string& name) const; + // Get the image layout of the given image at the end of this pass + VkImageLayout getFinalLayout(const std::string& name) const; + // Get the image layout of the given image before the given subpass starts + VkImageLayout getSubpassLayout(const std::string& name, int subpass) const; + + // Update the state of the given image's usage tracker. + void update(const std::string& name, MultiImageTracker& tracker) const; + + protected: + /** + * Some metadata about the usage of an image between subpasses. + */ + struct Usages { + Usages(const int last, const ImageUsage* prev, const ImageUsage* curr) : lastSubpass(last), lastUsage(*prev), currentUsage(*curr) {} + const int lastSubpass; + const ImageUsage& lastUsage; + const ImageUsage& currentUsage; + }; + + // Add the usage of an image in the pass to its' tracker. + void addUsage(std::string&& name, UsageTracker&& tracker); + + // Get the full history of the image's usages up to this rendering pass. + const UsageTracker& getHistory(const std::string& name) const; + + // Get the usage of an image at the start of the given pass. + const ImageUsage* getUsage(const std::string& name, int pass) const; + + // Retrieve image usage data, but only if the image is barriered at the given pass. + std::optional checkForSync(const std::string& name, int pass) const; + + // Validate that the subpass is valid for the given image. + // The meaning of includeVirtual is defined by the child implementation. + void validate(int pass, const std::string& image, bool includeVirtual) const; + + int getVirtualInitial() const { return -1; } + int getVirtualFinal() const { return numPasses; } + + protected: + std::map usageHistory; + const int numPasses; + }; + + /** + * The Common Pass implementation for Graphics passes; that is, render passes that output to color buffers + * for presentation to the screen, or to be used as textures in such. + * The instance of the GraphicsPass can be stored and reused to create multiple RenderPassBuilders. + * In this way it is essentially a RenderPassBuilderFactory. + */ + class GraphicsPass : public CommonPass { + public: + + using LocationGetter = std::function; + + explicit GraphicsPass(int passes) : CommonPass {passes} {} + + GraphicsPass(const GraphicsPass&) = delete; + GraphicsPass& operator=(const GraphicsPass&) = delete; + + // Get the default render ops for a color buffer. + static RenderPassBuilder::Attachment::OpsType getDefaultOps() { + return RenderPassBuilder::Attachment::ColorOps { VK_ATTACHMENT_LOAD_OP_CLEAR, VK_ATTACHMENT_STORE_OP_STORE }; + } + + // Get the default render ops for a stencil buffer. + static RenderPassBuilder::Attachment::OpsType getStencilOps() { + return RenderPassBuilder::Attachment::StencilDepthOps { VK_ATTACHMENT_LOAD_OP_CLEAR, VK_ATTACHMENT_STORE_OP_DONT_CARE, VK_ATTACHMENT_LOAD_OP_CLEAR, VK_ATTACHMENT_STORE_OP_DONT_CARE }; + } + + /** + * Add an image reference that is used in this render pass. + * @param name the name of the image used + * @param history the usage history of the image, for tracking purposes + * @param getter a function to get the location of the image, only if the image is used as a render target. + * @param ops optional; uses the static defaults if not present. + * @return the index into the VkAttachmentDescriptions. + */ + int add(const std::string& name, UsageTracker&& history, LocationGetter&& getter, const std::optional ops = std::nullopt); + + #define fluent GraphicsPass& + + // Specifies that the source image will be resolved to the single destination at the given pass. + fluent addMultisample(const std::string& source, const std::string& dest, int pass); + + // Build a RenderPassBuilder with the information provided so far. + std::unique_ptr build(int framebuffers); + + private: + struct AttachmentMeta { + int index; + LocationGetter getter; + vlkx::RenderPassBuilder::Attachment::OpsType ops; + std::map multisample; + }; + + void setAttachments(); + void setSubpasses(); + void setDependencies(); + + /** + * Find the first subpass where the given image is used as a render target. + * @param history the usage history of the image; what it was used at at each subpass. + * @return nullopt if the image was not used as a render target, the index of the subpass where it was, if not. + */ + std::optional getFirstRenderTarget(const UsageTracker& history) const; + + /** + * Return the operations that should be used for the given image attachment. + * If the user specified ops, it will be checekd against the history. + * @param name the name of the image to use as the attachment + * @param history the usage history of the attachment, for internal checks + * @param userOps operations to use for the image, as an optional override. + * @return the ColorOps to use for the given attachment. + */ + RenderPassBuilder::Attachment::OpsType getOps(const std::string& name, const UsageTracker& history, const std::optional& userOps) const; + + /** + * Get the usage type of the image. + * Assumption: an image is only ever used as a color OR depth stencil. Never both. + * Assumption: Multisample == RenderTarget + * @param name the name of the image to check + * @param history the history of the image's usages in the GPU. + * @return whether the image is a RenderTarget or a DepthStencil buffer. + */ + ImageUsage::Type getUsageType(const std::string& name, const UsageTracker& history) const; + + /** + * Ensure that the image is used as type at subpass in its' history. + */ + bool verifyImageUsage(const UsageTracker& history, int subpass, ImageUsage::Type type) const; + + /** + * Return whether the subpass is virtual. + * For a Render Pass, virtual means it is a preprocessing step. + */ + bool isVirtual(int subpass) const { + return subpass == getVirtualInitial() || subpass == getVirtualFinal(); + } + + /** + * Return the subpass index; for virtual passes, it uses an EXTERNAL subpass. + */ + uint32_t checkSubpass(int subpass) const { + return isVirtual(subpass) ? VK_SUBPASS_EXTERNAL : (uint32_t) subpass; + } + + /** + * Ensure that the image's usages are compatible with a render pass. + * For example, compute shader linear buffers cannot be used as render targets, etc. + */ + void verifyHistory(const std::string& image, const UsageTracker& history) const; + + std::map metas; + std::unique_ptr builder; + + }; + + + /** + * The Common Pass implementation for Compute Shaders. + * That is, shaders that do not write to color buffers. + * A subpass can execute multiple compute shaders unbarriered, which increases efficiency. + * We still need to transition images between passes when necessary, hence the wrapper. + */ + class ComputePass : public CommonPass { + public: + + ComputePass(const ComputePass&) = delete; + ComputePass& operator=(const ComputePass&) = delete; + + #define fluent ComputePass& + + /** + * Add the given image as an attachment to the compute shader pass. + * @param name the name of the image + * @param history the usage history of the image + * @return the ComputePass instance, for chaining. + */ + fluent add(std::string&& name, UsageTracker&& history); + fluent add(const std::string& name, UsageTracker&& history) { + return add(std::string(name), std::move(history)); + } + + /** + * Run computeOps, insert memory barriers to transition used images into the appropriate format. + * Images must be a superset of all images that were called with add(). + * Compute_ops must be equal to the number of subpasses. + * Commands must be recording. + * @param commands the command buffer to write into. + * @param queueFamily the family to use for inserting barriers + * @param images the list of images that were used in the compute pass + * @param computeOps the compute functions to upload to the GPU + */ + void execute(const VkCommandBuffer& commands, uint32_t queueFamily, const std::map& images, const std::vector>& computeOps) const; + + /** + * Insert a memory barrier, to transition the layout of the image from the previous to the curent. + * The barrier is performed using the given queue family. + * @param commands the command buffer to write into. + * @param queueFamily the family to use for inserting barriers. + * @param image the list of images that were used in the compute pass + * @param prev the previous usage of the image; the state being transitioned from + * @param current the new usage of the image; the state being transitioned to. + */ + void barrier(const VkCommandBuffer& commands, uint32_t queueFamily, const VkImage& image, const ImageUsage& prev, const ImageUsage& current) const; + + /** + * Verify whether the previous usages of the given image in its' history is compatible with a compute shader. + * For example, a fragment shader output image is not compatible. + * @param name the name of the image being checked + * @param history the usage history of the image/ + */ + void verify(const std::string& name, const UsageTracker& history) const; + }; +} \ No newline at end of file diff --git a/projs/shadow/shadow-engine/shadow-renderer/inc/vlkx/render/render_pass/GenericRenderPass.h b/projs/shadow/shadow-engine/shadow-renderer/inc/vlkx/render/render_pass/GenericRenderPass.h new file mode 100644 index 0000000..37764e6 --- /dev/null +++ b/projs/shadow/shadow-engine/shadow-renderer/inc/vlkx/render/render_pass/GenericRenderPass.h @@ -0,0 +1,238 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include "vlkx/vulkan/Tools.h" +#include "vlkx/vulkan/abstraction/Image.h" + +namespace vlkx { + + /** + * Gathers the operations that the GPU should perform when rendering to a framebuffer. + * Subpasses and dependencies are to be configured automatically using the builder below. + * RenderPass objects are disposable, and should be discarded when the framebuffer changes. + */ + class RenderPass { + public: + using RenderFunc = std::function; + + // Delete copy and move constructors to prevent the GPU getting confused with what we're trying to do + RenderPass(const RenderPass&) = delete; + RenderPass& operator=(const RenderPass&) = delete; + ~RenderPass(); + + RenderPass(int subpasses, VkRenderPass pass, std::vector clear, VkExtent2D ext, std::vector fbs, std::vector attachs) + : subpassCount(subpasses), renderPass(pass), clearValues(std::move(clear)), extent(ext), framebuffers(std::move(fbs)), attachments(std::move(attachs)) {} + + const VkRenderPass& operator*() const { return renderPass; } + + int getAttachsInSubpass(int subpass) const { + return attachments[subpass]; + } + + /** + * Upload all of the subpass render commands to the command buffer. + * The size of ops must be equal to the number of subpasses in this render pass. + * @param commands the command buffer to execute on; must be recording + * @param imageIndex the index of the image on the swapchain that we're rendering to; the target framebuffer. + * @param ops the render operations to add onto the command buffer. + */ + void execute(const VkCommandBuffer& commands, int imageIndex, std::vector ops) const; + + private: + // The number of sub-render-passes in this pass. + const int subpassCount; + // The VkRenderPass that this class wraps. + VkRenderPass renderPass; + // The clear values that will wipe all framebuffers to their empty states. + const std::vector clearValues; + // The size of the framebuffers (all are the same size) + const VkExtent2D extent; + // The framebuffers that we can render to + const std::vector framebuffers; + // The number of color attachments (sampled color images) in each subpass, by subpass index. + const std::vector attachments; + }; + + /** + * A stateful, fluent way to create Render Passes. + * This object can be stored and reused; when the window size changes, simply set the extent and + * export a new RenderPass to be used in the pipeline. + * + * Allows setting sub-passes, sub-pass dependencies, operations to read and write them, etc. + */ + class RenderPassBuilder { + public: + + /** + * Information required to define an attachment to be used in a render pass. + * Contains information on the layout, the operations to use on read and write, etc. + */ + struct Attachment { + // Operations to use on color attachments. + struct ColorOps { + VkAttachmentLoadOp LOAD; // Load data in the color attachment + VkAttachmentStoreOp STORE; // Store data into the color attachment + }; + + // Operations to use on depth and stencil buffers. + struct StencilDepthOps { + VkAttachmentLoadOp DEPTH_LOAD; // Load data in the depth attachment + VkAttachmentStoreOp DEPTH_STORE; // Store data in the depth attachment + VkAttachmentLoadOp STENCIL_LOAD; // Load data in the stencil attachment + VkAttachmentStoreOp STENCIL_STORE; // Store data in the stencil attachment + }; + + using OpsType = std::variant; + + // The operations that can be performed on this attachment + OpsType ops; + // The initial layout of an image in this attachment (pre-GPU upload) + VkImageLayout layoutInitial; + // The final layout of an image in this attachment (as seen by the shader) + VkImageLayout layoutFinal; + }; + + /** + * Describes attachments used in each subpass, in terms of Vulkan data. + * Attachment here is used in the conceptual sense, not referring to the Attachment struct above. + * + * If multisampleReferences is non-zero, its' size must be equal to colorReferences' size. + * Each index of multisampleReferences refers to the same-index colorReferences entry. + * + * If stencilDepthReference is non-zero, it is shared between all subpasses. + */ + struct SubpassAttachments { + std::vector colorReferences; + std::vector multisampleReferences; + std::optional stencilDepthReference; + }; + + /** + * Describes the dependencies between each sub-pass. + * It describes how each subpass will read or modify the data written by the last subpass, if at all. + * This dependency information can allow the GPU to run some passes in parallel, and enforce the + * strict ordering of those that require it. + */ + struct SubpassDependency { + + /** + * Defines some metadata about the subpass. + * Contains the index of the subpass, how it will use data from the last pass, and what exactly it will do. + */ + struct SubpassMeta { + /** + * Index of the subpass. + */ + uint32_t index; + + /** + * Describes how we want to modify the data passed to us from the last subpass. + * Will change how the next subpass will wait for the completion of this subpass, if at all. + * + * VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT: + * read/write to the color attachment + * + * VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT: + * VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT: + * read/write to the depth or stencil attachment + * + * VK_PIPELINE_STAGE_VERTEX_SHADER_BIT: + * VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT: + * read all attachments + * + */ + VkPipelineStageFlags stage; + + /** + * Describes how we want to synchronize with the subpass after this one. + * + * VK_ACCESS_SHADER_READ_BIT: + * VK_ACCESS_SHADER_WRITE_BIT: + * read a texture or write to a color buffer + * + * VK_ACCESS_COLOR_ATTACHMENT_READ_BIT: + * VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_READ_BIT: + * VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT: + * VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT: + * read/write to an attachment. + * + * VK_ACCESS_INPUT_ATTACHMENT_READ_BIT: + * for accessing the inputAttachment of a subpass. + * + * 0: + * do not write, but the next subpass will. + * Will automatically barrier the render pass. + */ + VkAccessFlags access; + }; + + SubpassMeta source; // The source subpass of this dependency (will take effect after this pass completes) + SubpassMeta destination; // The destination subpass of this dependency (will take effect before this pass) + VkDependencyFlags flags; // Other information that Vulkan needs to know about this dependency; for example, if we use an inputAttachment. + }; + + /** + * Describes a color attachment. + * Effectively RGB images that live entirely on the GPU. + * + * Describes the resolution mechanics of a multisampled image. + */ + struct ColorAttachmentMeta { + int location; // Where the GPU shaders expect this attachment to be available. + int descriptionIdx; // Index of this attachment in the VkAttachmentDescription data. + VkImageLayout layout; // Vulkan image layout. Shader optimized or host readable. + }; + + /** + * Create a list of VkAttachmentReference that describes the attachments used per subpass. + */ + static std::vector parseColorReferences(std::vector meta); + + /** + * Create a list of VkAttachmentReference that describes the multisampling setup. + */ + static std::vector parseMutisampling(int colorReferencesCount, std::vector meta); + + RenderPassBuilder(const RenderPassBuilder&) = delete; + RenderPassBuilder& operator=(const RenderPassBuilder&) = delete; + ~RenderPassBuilder() = default; + RenderPassBuilder() = default; + + /** Fluent API Features; chain calls to set data on the render pass.*/ + #define fluent RenderPassBuilder& + + // Set the number of framebuffers in the render pass + fluent setFramebufferCount(int count); + // Set an attachment description in the render pass + fluent setAttachment(int idx, const Attachment& attachment); + // Update the image backing an attachment. The function must be executable during execute() later on. + fluent updateAttachmentBacking(int idx, std::function&& getBacking); + // Set a specific subpass. Use the static parse methods to create these vectors. + fluent setSubpass(int idx, std::vector&& color, std::vector&& multisample, VkAttachmentReference& depthStencil); + // Add a dependency between two subpasses. + fluent addDependency(const SubpassDependency& dep); + + // Build the Render Pass with all the information given. + // Can be called multiple times with the same Builder. + [[nodiscard]] std::unique_ptr build() const; + + private: + // Number of framebuffers in the render pass + std::optional framebufferCount; + // Descriptions of used attachments + std::vector attachmentDescriptors; + // Functions to return attachment images. + std::vector> attachmentGetters; + // Values to clear all attachments + std::vector clearValues; + // Descriptions of subpasses. + std::vector subpassAttachments; + // Descriptions of subpass dependencies. + std::vector subpassDependencies; + }; +} \ No newline at end of file diff --git a/projs/shadow/shadow-engine/shadow-renderer/inc/vlkx/render/render_pass/ScreenRenderPass.h b/projs/shadow/shadow-engine/shadow-renderer/inc/vlkx/render/render_pass/ScreenRenderPass.h new file mode 100644 index 0000000..d431130 --- /dev/null +++ b/projs/shadow/shadow-engine/shadow-renderer/inc/vlkx/render/render_pass/ScreenRenderPass.h @@ -0,0 +1,130 @@ +#pragma once + +#include +#include +#include "GenericRenderPass.h" +#include "vlkx/vulkan/abstraction/ImageUsage.h" +#include "vlkx/vulkan/abstraction/Image.h" + +namespace vlkx { + + // A simple and versatile way to configure render passes. + // Intended to be used with the SimpleRenderPass and the ScreenRenderPass. + class RendererConfig { + public: + RendererConfig(std::vector>& destinations, bool toScreen = true) : renderImages(destinations) { + numOpaquePasses = 1; + rendersToScreen = toScreen; + } + + RendererConfig(int passCount, std::vector>& destinations, bool toScreen = true, std::optional firstTransparent = std::nullopt, std::optional firstOverlay = std::nullopt); + + // Get the number of passes that use the depth buffer. + int depthPasses() const { + return numOpaquePasses + numTransparentPasses; + } + + // Get the total number of passes. + int passes() const { + return depthPasses() + numOverlayPasses; + } + + // Get whether any passes use the depth buffer. + bool usesDepth() const { + return depthPasses() > 0; + } + + // Create the render pass builder. Can be called multiple times. + void build(); + + RendererConfig(RendererConfig&) noexcept = default; + RendererConfig(const RendererConfig&) = default; + + int numOpaquePasses = 0; + int numTransparentPasses = 0; + std::vector>& renderImages; + bool rendersToScreen; + private: + int numOverlayPasses = 0; + }; + + /** + * Stores all of the information required to use an attachment. + * This is heavy, so is only used when the image is being finalized. + */ + struct AttachmentConfig { + AttachmentConfig(std::string_view name, std::optional* index) + : name(name), index(*index) {} + + AttachmentConfig& setOps(const RenderPassBuilder::Attachment::OpsType& ops) { + loadStoreOps = ops; + return *this; + } + + AttachmentConfig& setUsage(const ImageUsage& final) { + finalUsage = final; + return *this; + } + + std::string name; + std::optional& index; + std::optional loadStoreOps; + std::optional finalUsage; + }; + + /** + * A lighter version of the Attachment, used primarily in the On-Screen Pass Manager. + */ + struct Attachment { + explicit Attachment(std::string_view image) : name(image) {} + + // Adds the image to the tracker and initializes state. + void add(MultiImageTracker& tracker, const Image& image) { + tracker.track(name, image.getUsage()); + } + + AttachmentConfig makeConfig() { return { name, &index }; } + + const std::string name; + std::optional index; + }; + + // Manages Render Passes that will output to the screen. + // This is necessarily exclusively a graphical pass. + // If necessary, a depth and stencil buffer will be maintained. + // The color buffer is automatically assumed to be the swapchain. + class ScreenRenderPassManager { + public: + explicit ScreenRenderPassManager(RendererConfig renderConfig) : config(renderConfig) {} + + ScreenRenderPassManager(const ScreenRenderPassManager&) = delete; + ScreenRenderPassManager& operator=(const ScreenRenderPassManager&) = delete; + + // Initialize the render pass we're managing. + void initializeRenderPass(); + + std::unique_ptr& getPass() { return pass; } + + private: + + // Prepare the Render Pass builder. + void preparePassBuilder(); + + const RendererConfig config; + + Attachment destinationInfo { "Destination" }; + Attachment multisampleInfo { "Multisample" }; + Attachment depthStencilInfo { "Depth-Stencil" }; + + std::unique_ptr depthStencilImage; + std::unique_ptr passBuilder; + std::unique_ptr pass; + }; + + /** + * A utility namespace used to house a constructor for creating a "simple" render pass with all the defaults. + */ + namespace SimpleRenderPass { + static std::unique_ptr createBuilder(int framebuffers, const RendererConfig& config, const AttachmentConfig& color, const AttachmentConfig* multisample, const AttachmentConfig* depthStencil, MultiImageTracker& tracker); + } +} \ No newline at end of file diff --git a/projs/shadow/shadow-engine/shadow-renderer/inc/vlkx/render/shader/Descriptor.h b/projs/shadow/shadow-engine/shadow-renderer/inc/vlkx/render/shader/Descriptor.h deleted file mode 100644 index 88d70f3..0000000 --- a/projs/shadow/shadow-engine/shadow-renderer/inc/vlkx/render/shader/Descriptor.h +++ /dev/null @@ -1,30 +0,0 @@ -#pragma once - -#include -#include - -class Descriptor { -public: - Descriptor(); - ~Descriptor(); - - // Global descriptor bindings - VkDescriptorSetLayout layout; - VkDescriptorPool pool; - VkDescriptorSet set; - - // Allocate and prepare the descriptors for a simple (uniform buffer only) descriptor. - void createAndAllocateSimple(uint32_t imageCount); - - // Fill the Descriptors with the given uniforms - void populate(uint32_t imageCount, VkBuffer uniforms, size_t bufferSize); - void destroy(); - -private: - - // Set up the layout for a single UBO - void createSimpleLayout(); - // Setup the pool for a single UBO - void createSimplePool(uint32_t imageCount); - -}; \ No newline at end of file diff --git a/projs/shadow/shadow-engine/shadow-renderer/inc/vlkx/render/shader/GeoBuffers.h b/projs/shadow/shadow-engine/shadow-renderer/inc/vlkx/render/shader/GeoBuffers.h deleted file mode 100644 index e527e80..0000000 --- a/projs/shadow/shadow-engine/shadow-renderer/inc/vlkx/render/shader/GeoBuffers.h +++ /dev/null @@ -1,37 +0,0 @@ -#pragma once - -#include -#include - -#include -#include - -// Contains memory and objects required to store information about geometry. -class GeoBuffers { -public: - - GeoBuffers(); - ~GeoBuffers(); - - // Program and virtual memory for vertex data. - std::vector vertices; - VkTools::ManagedBuffer vertexBuffer; - - // Program and virtual memory for indices data. - std::vector indices; - VkTools::ManagedBuffer indexBuffer; - - // Virtual memory for uniforms - translation matrices, etc. - VkTools::ManagedBuffer uniformBuffer; - - void createBuffers(Geo::MeshType type); - - void destroy(); - -private: - - void createVertexBuffer(); - void createIndexBuffer(); - void createUniformBuffer(); - -}; \ No newline at end of file diff --git a/projs/shadow/shadow-engine/shadow-renderer/inc/vlkx/render/shader/Pipeline.h b/projs/shadow/shadow-engine/shadow-renderer/inc/vlkx/render/shader/Pipeline.h index eee2fb7..569f087 100644 --- a/projs/shadow/shadow-engine/shadow-renderer/inc/vlkx/render/shader/Pipeline.h +++ b/projs/shadow/shadow-engine/shadow-renderer/inc/vlkx/render/shader/Pipeline.h @@ -1,31 +1,205 @@ #pragma once #include #include +#include #include +#include "shadow/util/RefCounter.h" +#include "vlkx/vulkan/VulkanModule.h" -class Pipeline { -public: - Pipeline(); - ~Pipeline(); +namespace vlkx { + class Pipeline; - // The active Graphics Pipeline layout. - VkPipelineLayout layout; - // The active Graphics Pipeline instance. - VkPipeline pipeline; + // A simple wrapper for the shader module used by the pipeline. + class ShaderModule { + public: + using CountedShader = shadowutil::RefCounter; + using ReleasePool = CountedShader::AutoRelease; - // Create the layout and pipeline for a vertex renderer - void create(VkExtent2D extent, VkDescriptorSetLayout set, VkRenderPass renderPass); + ShaderModule(const std::string& path); - void destroy(); + ShaderModule(const ShaderModule&) = delete; + ShaderModule& operator=(const ShaderModule&) = delete; -private: + ~ShaderModule() { + vkDestroyShaderModule(VulkanModule::getInstance()->getDevice()->logical, shader, nullptr); + } - std::vector readFile(const std::string& filename); - VkShaderModule createShaderModule(const std::vector& code); + const VkShaderModule& operator*() const { return shader; } - void createPipelineLayout(VkDescriptorSetLayout set); - - // For rendering objects that use traditional vertex-based mesh geometry. - // See Geo::Vertex for the uniform bindings. - void createVertexPipeline(VkExtent2D extent, VkRenderPass renderPass); -}; \ No newline at end of file + private: + VkShaderModule shader; + }; + + class PipelineBuilder { + public: + PipelineBuilder(const PipelineBuilder&) = delete; + PipelineBuilder& operator=(const PipelineBuilder&) = delete; + + virtual ~PipelineBuilder() { + vkDestroyPipelineCache(VulkanModule::getInstance()->getDevice()->logical, cache, nullptr); + } + + virtual std::unique_ptr build() const = 0; + + protected: + PipelineBuilder(std::optional maxCache); + + void setName(std::string&& n) { name = std::move(name); } + + void setLayout(std::vector&& descLayouts, std::vector&& constants); + + const std::string& getName() const { return name; } + + bool hasLayout() const { return layoutInfo.has_value(); } + const VkPipelineLayoutCreateInfo& getLayout() const { return layoutInfo.value(); } + + private: + VkPipelineCache cache; + std::string name; + + std::optional layoutInfo; + std::vector descLayouts; + std::vector constants; + }; + + /** + * Use when creating Graphics pipelines. + * Internal state is preserved so that multiple pipelines can be created with one builder. + * However, shaders are single-usage. Bind a new shader before claling build again. + * See ShaderModule for more information, and how to change this. + */ + class GraphicsPipelineBuilder : public PipelineBuilder { + public: + struct Viewport { + VkViewport viewport; + VkRect2D scissor; + }; + + explicit GraphicsPipelineBuilder(std::optional maxCache = std::nullopt); + + GraphicsPipelineBuilder(const GraphicsPipelineBuilder&) = delete; + GraphicsPipelineBuilder& operator=(const GraphicsPipelineBuilder&) = delete; + + #define fluent GraphicsPipelineBuilder& + + fluent name(std::string&& name); + fluent depthTest(bool enable, bool write); + fluent stencilTest(bool enable); + fluent multiSample(VkSampleCountFlagBits samples); + fluent topology(VkPrimitiveTopology topology); + fluent stencilOp(const VkStencilOpState& state, VkStencilFaceFlags flags); + + fluent addVertex(uint32_t bindPoint, VkVertexInputBindingDescription&& desc, std::vector&& attrs); + fluent layout(std::vector&& descLayouts, std::vector&& constants); + fluent viewport(const Viewport& port, bool flipY = true); + fluent renderPass(const VkRenderPass& pass, uint32_t subpass); + + fluent colorBlend(std::vector&& states); + fluent shader(VkShaderStageFlagBits stage, std::string&& file); + + std::unique_ptr build() const override; + + private: + struct PassInfo { + VkRenderPass pass; + uint32_t subpass; + }; + + VkPipelineInputAssemblyStateCreateInfo assemblyInfo; + VkPipelineRasterizationStateCreateInfo rasterizationInfo; + VkPipelineMultisampleStateCreateInfo multisampleInfo; + VkPipelineDepthStencilStateCreateInfo depthStencilInfo; + VkPipelineDynamicStateCreateInfo dynamicStateInfo; + + std::vector bindingDescs; + std::vector attrDescs; + + std::optional viewportMeta; + std::optional passMeta; + std::vector blendStates; + std::map shaders; + }; + + /** + * Use when creating Compute Shader pipelines. + * Internal state is preserved so that multiple pipelines can be created with one builder. + * However, shaders are single-usage. Bind a new shader before claling build again. + * See ShaderModule for more information, and how to change this. + */ + class ComputePipelineBuilder : public PipelineBuilder { + public: + explicit ComputePipelineBuilder(std::optional maxCache = std::nullopt) : PipelineBuilder(maxCache) {} + + ComputePipelineBuilder(const ComputePipelineBuilder&) = delete; + ComputePipelineBuilder& operator=(const ComputePipelineBuilder&) = delete; + + #define fluent ComputePipelineBuilder& + + fluent name(std::string&& name); + fluent layout(std::vector&& descLayouts, std::vector&& pushConstants); + fluent shader(std::string&& path); + + std::unique_ptr build() const override; + + private: + std::optional shaderPath; + }; + + /** + * Pipeline configures: + * - Shader Stages + * - Fixed Function stages + * - Vertex input bindings + * - Vertex attributes + * - Assembly + * - Tesselation + * - Viewport and Scissor + * - Rasterization + * - Multisampling + * - Depth testing + * - Stencil testing + * - Color blending + * - Dynamic states + * - Pipeline layout + * - Descriptor set layout + * - Push constant ranges + * + * Create a Pipeline with one of the builders above. + */ + class Pipeline { + public: + Pipeline(const Pipeline&) = delete; + Pipeline& operator=(const Pipeline&) = delete; + + ~Pipeline(); + + void bind(const VkCommandBuffer& buffer) const; + + const VkPipeline& operator*() const { return pipeline; } + const VkPipelineLayout& getLayout() const { return layout; } + VkPipelineBindPoint getBind() const { return bindPoint; } + + static VkPipelineColorBlendAttachmentState getAlphaBlendState(bool blending) { + return { + blending, VK_BLEND_FACTOR_SRC_ALPHA, VK_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA, + VK_BLEND_OP_ADD, VK_BLEND_FACTOR_ONE, VK_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA, + VK_BLEND_OP_ADD, VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_G_BIT | VK_COLOR_COMPONENT_B_BIT | VK_COLOR_COMPONENT_A_BIT + }; + } + + private: + friend std::unique_ptr GraphicsPipelineBuilder::build() const; + friend std::unique_ptr ComputePipelineBuilder::build() const; + + Pipeline(std::string name, const VkPipeline& line, const VkPipelineLayout& lay, VkPipelineBindPoint bPoint) + : name(std::move(name)), pipeline(line), layout(lay), bindPoint(bPoint) {} + + const std::string name; + // The active Pipeline layout. + const VkPipelineLayout layout; + // The active Pipeline instance. + const VkPipeline pipeline; + // Whether this is a graphics or compute pipeline + const VkPipelineBindPoint bindPoint; + }; +} diff --git a/projs/shadow/shadow-engine/shadow-renderer/inc/vlkx/render/texture/RenderTexture.h b/projs/shadow/shadow-engine/shadow-renderer/inc/vlkx/render/texture/RenderTexture.h deleted file mode 100644 index 1e76fb2..0000000 --- a/projs/shadow/shadow-engine/shadow-renderer/inc/vlkx/render/texture/RenderTexture.h +++ /dev/null @@ -1,46 +0,0 @@ -#pragma once - -#include -#include -#include - -class RenderTexture { -public: - // Create all image views and all framebuffers from a set of images, a format, a size, and the current rendering pass. - virtual void createViewsAndFramebuffer(std::vector images, VkFormat format, VkExtent2D extent, VkRenderPass pass) = 0; - // Create views for swapChainImages with the given format. - virtual void createViews(VkFormat format) = 0; - // Create a framebuffer for the swapChainImages, for the given pass. - virtual void createFramebuffer(VkExtent2D extent, VkRenderPass pass) = 0; - - virtual VkFramebuffer getFramebuffer(int ID) = 0; - - virtual void destroy() = 0; -}; - -class SingleRenderTexture : public RenderTexture { -public: - SingleRenderTexture(); - ~SingleRenderTexture(); - - // A list of all images involved with this texture (color, normals, etc) - std::vector swapChainImages; - // The sizes of all attached images. - VkExtent2D swapChainImageExtent; - - // Views - mipmaps, portions, crops, etc of the attached images. - std::vector swapChainImageViews; - // Framebuffers containing the images that can be bound and rendered from. - std::vector swapChainFramebuffers; - - VkFramebuffer getFramebuffer(int ID) override { return swapChainFramebuffers.at(ID); } - - // Create all image views and all framebuffers from a set of images, a format, a size, and the current rendering pass. - void createViewsAndFramebuffer(std::vector images, VkFormat format, VkExtent2D extent, VkRenderPass pass) override; - // Create views for swapChainImages with the given format. - void createViews(VkFormat format) override; - // Create a framebuffer for the swapChainImages, for the given pass. - void createFramebuffer(VkExtent2D extent, VkRenderPass pass) override; - - void destroy() override; -}; \ No newline at end of file diff --git a/projs/shadow/shadow-engine/shadow-renderer/inc/vlkx/vulkan/CommandBuffer.h b/projs/shadow/shadow-engine/shadow-renderer/inc/vlkx/vulkan/CommandBuffer.h deleted file mode 100644 index 1811ac6..0000000 --- a/projs/shadow/shadow-engine/shadow-renderer/inc/vlkx/vulkan/CommandBuffer.h +++ /dev/null @@ -1,23 +0,0 @@ -#pragma once - -#include -#include - -class CommandBuffer { -public: - - CommandBuffer(); - ~CommandBuffer(); - - VkCommandPool commands; - std::vector buffers; - - void createCommandPoolAndBuffers(size_t images); - void beginCommandBuffer(VkCommandBuffer buffer); - void endCommandBuffer(VkCommandBuffer buffer); - - void createCommandPool(); - void allocateCommandBuffers(size_t size); - - void destroy(); -}; \ No newline at end of file diff --git a/projs/shadow/shadow-engine/shadow-renderer/inc/vlkx/vulkan/SwapChain.h b/projs/shadow/shadow-engine/shadow-renderer/inc/vlkx/vulkan/SwapChain.h index 7f715c1..b43cc3a 100644 --- a/projs/shadow/shadow-engine/shadow-renderer/inc/vlkx/vulkan/SwapChain.h +++ b/projs/shadow/shadow-engine/shadow-renderer/inc/vlkx/vulkan/SwapChain.h @@ -4,6 +4,7 @@ #include #include #include +#include "vlkx/vulkan/abstraction/Image.h" class SwapChain { public: @@ -14,7 +15,8 @@ public: VkFormat format; VkExtent2D extent; - std::vector images; + std::vector> images; + std::unique_ptr multisampleImg; VkSurfaceFormatKHR chooseFormat(const std::vector& availableFormats); VkPresentModeKHR chooseMode(const std::vector& availableModes); diff --git a/projs/shadow/shadow-engine/shadow-renderer/inc/vlkx/vulkan/Tools.h b/projs/shadow/shadow-engine/shadow-renderer/inc/vlkx/vulkan/Tools.h index 4a1f3c6..3522e69 100644 --- a/projs/shadow/shadow-engine/shadow-renderer/inc/vlkx/vulkan/Tools.h +++ b/projs/shadow/shadow-engine/shadow-renderer/inc/vlkx/vulkan/Tools.h @@ -4,19 +4,12 @@ #include #include -#include - #include +#include "VulkanDevice.h" +#include "exports.h" + namespace VkTools { - extern VmaAllocator g_allocator; - extern VkInstance g_Instance; - extern VkPhysicalDevice g_PhysicalDevice; - extern VkDevice g_Device; - extern uint32_t g_QueueFamily; - extern VkQueue g_Queue; - extern VkDebugReportCallbackEXT g_DebugReport; - struct ManagedImage { VkImage image; VmaAllocation allocation; @@ -27,194 +20,20 @@ namespace VkTools { VmaAllocation allocation; }; - ManagedImage createImage(VkFormat format, VkImageUsageFlags flags, VkExtent3D extent, VkDevice device); - VkSampler createSampler(VkFilter filters, VkSamplerAddressMode mode); - VkImageView createImageView(VkImage image, VkFormat format, VkImageAspectFlags flags, VkDevice device); - ManagedBuffer createGPUBuffer(VkDeviceSize size, VkBufferUsageFlags usage, VkMemoryPropertyFlags properties, VkDevice logicalDevice, VkPhysicalDevice physicalDevice, bool hostVisible = true); - uint32_t findMemoryIndex(uint32_t type, VkMemoryPropertyFlags properties, VkPhysicalDevice physicalDevice); - VkCommandBuffer createTempCommandBuffer(VkCommandPool pool, VkDevice logical); - void executeAndDeleteTempBuffer(VkCommandBuffer buffer, VkCommandPool pool, VkQueue queue, VkDevice logicalDevice); - void copyGPUBuffer(VkBuffer source, VkBuffer dest, VkDeviceSize length, VkDevice logical, VkQueue graphicsQueue, uint32_t queueIndex); + extern API VmaAllocator allocator; + ManagedImage createImage(VkFormat format, VkImageUsageFlags flags, VkExtent3D extent); - #ifdef VKTOOLS_IMPLEMENTATION - ManagedImage createImage(VkFormat format, VkImageUsageFlags flags, VkExtent3D extent, VkDevice device) { - // Set up image metadata - VkImageCreateInfo info = {}; - info.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO; - info.pNext = nullptr; - info.format = format; - info.extent = extent; - info.mipLevels = 1; - info.arrayLayers = 1; - info.samples = VK_SAMPLE_COUNT_1_BIT; - info.tiling = VK_IMAGE_TILING_OPTIMAL; - info.usage = flags; + VkSampler createSampler(VkFilter filters, VkSamplerAddressMode mode, uint32_t mipping, VkDevice device); - // Prepare the managed image - ManagedImage image {}; + VkImageView createImageView(VkImage image, VkFormat format, VkImageAspectFlags flags, uint32_t mipping, uint32_t layers, + VkDevice device); - // Set up image allocation - VmaAllocationCreateInfo allocateInfo = {}; - allocateInfo.usage = VMA_MEMORY_USAGE_GPU_ONLY; + ManagedBuffer createGPUBuffer(VkDeviceSize size, VkBufferUsageFlags usage, VkMemoryPropertyFlags properties, + VkDevice logicalDevice, VkPhysicalDevice physicalDevice, bool hostVisible = true); - // Allocate + create the image - vmaCreateImage(g_allocator, &info, &allocateInfo, &image.image, &image.allocation, nullptr); + void immediateExecute(const std::function &execute, VulkanDevice *dev); - return image; + void copyGPUBuffer(VkBuffer source, VkBuffer dest, VkDeviceSize length, VulkanDevice *dev); - } - - VkSampler createSampler(VkFilter filters, VkSamplerAddressMode mode) { - VkSamplerCreateInfo info = {}; - info.sType = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO; - info.pNext = nullptr; - info.magFilter = filters; - info.minFilter = filters; - info.addressModeU = mode; - info.addressModeV = mode; - info.addressModeW = mode; - - VkSampler sampler; - vkCreateSampler(g_Device, &info, nullptr, &sampler); - - return sampler; - } - - VkImageView createImageView(VkImage image, VkFormat format, VkImageAspectFlags flags, VkDevice device) { - // Raw information about the image - VkImageViewCreateInfo viewInfo = {}; - viewInfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO; - viewInfo.image = image; - viewInfo.viewType = VK_IMAGE_VIEW_TYPE_2D; - viewInfo.format = format; - - // Information about the things we want to create - size, mip levels. - viewInfo.subresourceRange.aspectMask = flags; - viewInfo.subresourceRange.baseMipLevel = 0; - viewInfo.subresourceRange.levelCount = 1; - viewInfo.subresourceRange.baseArrayLayer = 0; - viewInfo.subresourceRange.layerCount = 1; - - VkImageView imageView; - if (vkCreateImageView(device, &viewInfo, nullptr, &imageView) != VK_SUCCESS) - throw std::runtime_error("Failed to create texture image view."); - - return imageView; - } - - ManagedBuffer createGPUBuffer(VkDeviceSize size, VkBufferUsageFlags usage, VkMemoryPropertyFlags properties, VkDevice logicalDevice, VkPhysicalDevice physicalDevice, bool hostVisible) { - // Prepare for creation of a buffer - VkBufferCreateInfo bufferInfo = {}; - bufferInfo.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO; - bufferInfo.size = size; - bufferInfo.usage = usage; - bufferInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE; - - ManagedBuffer buffer; - - VmaAllocationCreateInfo vmaInfo = {}; - vmaInfo.usage = hostVisible ? VMA_MEMORY_USAGE_CPU_ONLY : VMA_MEMORY_USAGE_GPU_ONLY; - vmaInfo.requiredFlags = properties; - - // Create the buffer. - if (vmaCreateBuffer(g_allocator, &bufferInfo, &vmaInfo, &buffer.buffer, &buffer.allocation, nullptr) != VK_SUCCESS) - throw std::runtime_error("Unable to create GPU buffer"); - - return buffer; - } - - uint32_t findMemoryIndex(uint32_t type, VkMemoryPropertyFlags properties, VkPhysicalDevice physicalDevice) { - // Get the physical properties of the device. - VkPhysicalDeviceMemoryProperties physProperties; - vkGetPhysicalDeviceMemoryProperties(physicalDevice, &physProperties); - - // Iterate the device and search for a suitable index - for (uint32_t i = 0; i < physProperties.memoryTypeCount; i++) - // If the type matches, and the properties are what we desire, then ship it. - if ((type & (1 << i)) && ((physProperties.memoryTypes[i].propertyFlags & properties) == properties)) - return i; - - throw std::runtime_error("Unable to find a suitable memory type on the physical device."); - } - - VkCommandBuffer createTempCommandBuffer(VkCommandPool pool, VkDevice logical) { - // Prepare to allocate a command buffer - VkCommandBufferAllocateInfo allocateInfo = {}; - allocateInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO; - allocateInfo.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY; - allocateInfo.commandPool = pool; - allocateInfo.commandBufferCount = 1; - - // Allocate the buffer - VkCommandBuffer buffer; - vkAllocateCommandBuffers(logical, &allocateInfo, &buffer); - - // Prepare to begin the new buffer. - VkCommandBufferBeginInfo beginInfo = {}; - beginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO; - beginInfo.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT; - - // Begin listening on the new buffer. - vkBeginCommandBuffer(buffer, &beginInfo); - - return buffer; - } - - void executeAndDeleteTempBuffer(VkCommandBuffer buffer, VkCommandPool pool, VkQueue queue, VkDevice logicalDevice) { - // Stop listening on the buffer - vkEndCommandBuffer(buffer); - - // Prepare to execute the commands in the buffer - VkSubmitInfo submitInfo = {}; - submitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO; - submitInfo.commandBufferCount = 1; - submitInfo.pCommandBuffers = &buffer; - - // Submit the commands to be executed - vkQueueSubmit(queue, 1, &submitInfo, VK_NULL_HANDLE); - - // Wait for the GPU to finish executing - vkQueueWaitIdle(queue); - - // Delete the now unusable buffers - vkFreeCommandBuffers(logicalDevice, pool, 1, &buffer); - } - - void copyGPUBuffer(VkBuffer source, VkBuffer dest, VkDeviceSize length, VkDevice logical, VkQueue graphicsQueue, uint32_t queueIndex) { - - // Prepare to create a temporary command pool. - VkCommandPool pool; - VkCommandPoolCreateInfo poolCreateInfo = {}; - poolCreateInfo.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO; - poolCreateInfo.queueFamilyIndex = queueIndex; - poolCreateInfo.flags = 0; - - // Create the pool - if (vkCreateCommandPool(logical, &poolCreateInfo, nullptr, &pool) != VK_SUCCESS) - throw std::runtime_error("Unable to allocate a temporary command pool"); - - // Allocate a buffer - VkCommandBuffer commands = createTempCommandBuffer(pool, logical); - - // ------ Commands are saved into the commands field ------ // - - // Prepare to copy the data between buffers - VkBufferCopy copyInfo = {}; - copyInfo.srcOffset = 0; - copyInfo.dstOffset = 0; - copyInfo.size = length; - - // Copy the data. - vkCmdCopyBuffer(commands, source, dest, 1, ©Info); - - // ------ Commands are no longer saved into the commands field ------ // - - executeAndDeleteTempBuffer(commands, pool, graphicsQueue, logical); - - // Cleanup the temporary buffer and pool we created - vkDestroyCommandPool(logical, pool, nullptr); - } - - #endif } \ No newline at end of file diff --git a/projs/shadow/shadow-engine/shadow-renderer/inc/vlkx/vulkan/ValidationAndExtension.h b/projs/shadow/shadow-engine/shadow-renderer/inc/vlkx/vulkan/ValidationAndExtension.h index 035280a..0407373 100644 --- a/projs/shadow/shadow-engine/shadow-renderer/inc/vlkx/vulkan/ValidationAndExtension.h +++ b/projs/shadow/shadow-engine/shadow-renderer/inc/vlkx/vulkan/ValidationAndExtension.h @@ -11,7 +11,8 @@ public: ~ValidationAndExtension(); const std::vector requiredValidations = { - "VK_LAYER_KHRONOS_validation" + "VK_LAYER_KHRONOS_validation", + //"VK_LAYER_LUNARG_api_dump" }; VkDebugReportCallbackEXT callback; diff --git a/projs/shadow/shadow-engine/shadow-renderer/inc/vlkx/vulkan/VulkanDevice.h b/projs/shadow/shadow-engine/shadow-renderer/inc/vlkx/vulkan/VulkanDevice.h index 04e7a02..b4a409b 100644 --- a/projs/shadow/shadow-engine/shadow-renderer/inc/vlkx/vulkan/VulkanDevice.h +++ b/projs/shadow/shadow-engine/shadow-renderer/inc/vlkx/vulkan/VulkanDevice.h @@ -30,6 +30,7 @@ public: /** Physical Devices **/ VkPhysicalDevice physical; + VkPhysicalDeviceLimits limits; SwapChainMeta swapChain; QueueFamilies queueData; diff --git a/projs/shadow/shadow-engine/shadow-renderer/inc/vlkx/vulkan/VulkanManager.h b/projs/shadow/shadow-engine/shadow-renderer/inc/vlkx/vulkan/VulkanManager.h deleted file mode 100644 index 1915521..0000000 --- a/projs/shadow/shadow-engine/shadow-renderer/inc/vlkx/vulkan/VulkanManager.h +++ /dev/null @@ -1,95 +0,0 @@ -#pragma once - -#include -#include -#include -#include -#include -#include - -#include -#include - -#include - -#include - -class VulkanManager { -public: - VulkanManager(); - ~VulkanManager(); - -#ifdef _DEBUG - static const bool validationRequired = true; -#else - static const bool validationRequired = false; -#endif - - // VulkanManager is a singleton class. - static VulkanManager* instance; - static VulkanManager* getInstance(); - - // Initialize all Vulkan context and prepare validations in debug mode. - void initVulkan(SDL_Window* window); - void createAppAndVulkanInstance(bool enableValidation, ValidationAndExtension* validations); - - // Start and end a frame render. - void startDraw(); - void endDraw(); - - // Cleanup after the application has closed. - void cleanup(); - - void useRayTrace() { rayTraceMode = true; } - - VkInstance getVulkan() { return vulkan; } - VulkanDevice* getDevice() { return device; } - SwapChain* getSwapchain() { return swapchain; } - RenderPass* getRenderPass() { return renderPass; } - VkCommandBuffer getCurrentCommandBuffer() { return currentCommandBuffer; } - VmaAllocator getAllocator() { return allocator; } - RenderTexture* getRenderTarget() { return renderTexture; } - -private: - // To keep track of the window during... stuff - SDL_Window* wnd; - - // To handle the validation of Vulkan API usage (because fuck you, it's your problem now) - ValidationAndExtension* validators{}; - // To manage interaction with the hardware - VulkanDevice* device{}; - // To handle the framebuffers - SwapChain* swapchain{}; - // To handle the timing of rendering - RenderPass* renderPass{}; - - // To handle automatic management of memory. - VmaAllocator allocator{}; - - // The default RenderTexture, mirroring the SwapChain to the viewport. - RenderTexture* renderTexture; - // The command buffers used when telling the firmware to do things for us. - CommandBuffer* buffers{}; - - // To manage the Vulkan context that was passed to us by the API - VkInstance vulkan{}; - // To manage the canvas that was given to us by GLFW - VkSurfaceKHR surface{}; - - // The index of the texture that is currently being used by the GPU. - uint32_t imageIndex = 0; - // The command buffer currently being used by the GPU. - VkCommandBuffer currentCommandBuffer{}; - - // The maximum number of frames that can be dealt with at a time. - const int MAX_FRAMES = 2; // Double-buffering requires two frames in memory - // Raised when a new image is available - VkSemaphore newImageSem{}; - // Raised when a render is finished - VkSemaphore renderDoneSem{}; - // Stores fences for frames that are currently "in flight". - std::vector inFlight; - - bool rayTraceMode; - -}; \ No newline at end of file diff --git a/projs/shadow/shadow-engine/shadow-renderer/inc/vlkx/vulkan/VulkanModule.h b/projs/shadow/shadow-engine/shadow-renderer/inc/vlkx/vulkan/VulkanModule.h new file mode 100644 index 0000000..6bf87d7 --- /dev/null +++ b/projs/shadow/shadow-engine/shadow-renderer/inc/vlkx/vulkan/VulkanModule.h @@ -0,0 +1,99 @@ +#pragma once + +#include +#include + +#include +#include + +#include +#include +#include "SwapChain.h" + +namespace vlkx { class ScreenRenderPassManager; } + +class VulkanModule : public ShadowEngine::RendererModule { + SHObject_Base(VulkanModule); +public: + + VulkanModule(); + ~VulkanModule() override; + +#ifdef _DEBUG + static const bool validationRequired = true; +#else + static const bool validationRequired = false; +#endif + + void PreInit() override; + + void Init() override; + + void Recreate() override; + + void Update(int frame) override; + + void PreRender() override; + + void Render(VkCommandBuffer& commands, int frame) override; + + void OverlayRender() override; + + void LateRender(VkCommandBuffer& commands, int frame) override; + + void AfterFrameEnd() override; + + void Destroy() override; + + void Event(SDL_Event* e) override; + + void BeginRenderPass(const std::unique_ptr& commands) override; + + void EnableEditor() override; + + VkExtent2D GetRenderExtent() override; + + // VulkanModule is a singleton class. + static VulkanModule* instance; + static VulkanModule* getInstance(); + + // Initialize all Vulkan context and prepare validations in debug mode. + void initVulkan(SDL_Window* window); + void createAppAndVulkanInstance(bool enableValidation, ValidationAndExtension* validations); + + // Start and end a frame render. + void startDraw(); + void endDraw(); + + // Cleanup after the application has closed. + void cleanup(); + + VkInstance getVulkan() { return vulkan; } + VulkanDevice* getDevice() { return device; } + SwapChain* getSwapchain() { return swapchain; } + VmaAllocator getAllocator() { return allocator; } + SDL_Window* getWind() { return wnd; } + const std::unique_ptr& getRenderPass(); + + +private: + bool editorEnabled = false; + std::vector editorRenderPlanes; + std::vector> editorContentFrames; + + // The SDL Window contains the size of the drawable area. + SDL_Window* wnd; + // To handle the validation of Vulkan API usage + ValidationAndExtension* validators{}; + // To manage interaction with the hardware + VulkanDevice* device{}; + // To handle the framebuffers + SwapChain* swapchain{}; + // To handle automatic management of memory. + VmaAllocator allocator{}; + // To manage the Vulkan context that was passed to us by the API + VkInstance vulkan{}; + // To manage the canvas that was given to us by GLFW + VkSurfaceKHR surface{}; + +}; \ No newline at end of file diff --git a/projs/shadow/shadow-engine/shadow-renderer/inc/vlkx/vulkan/abstraction/Buffer.h b/projs/shadow/shadow-engine/shadow-renderer/inc/vlkx/vulkan/abstraction/Buffer.h new file mode 100644 index 0000000..ecd3d36 --- /dev/null +++ b/projs/shadow/shadow-engine/shadow-renderer/inc/vlkx/vulkan/abstraction/Buffer.h @@ -0,0 +1,412 @@ +#pragma once + +#include +#include +#include +#include +#include "vlkx/vulkan/Tools.h" + +namespace vlkx { + // Root buffer class. + // Used to store & move data between the CPU and GPU. + // Basically; hotspot! + // Utilities and subclasses exist to optimise and speed up transfer and management of data in bulk. + class Buffer { + public: + // Metadata of CPU->GPU data copying. + struct CopyMeta { + const void* data; // The start of data in RAM + VkDeviceSize length; // The amount of data to move + VkDeviceSize start; // The start (destination) in GPU memory + }; + + // Metadata of bulk CPU->GPU data copying. + struct BulkCopyMeta { + VkDeviceSize length; // The total data size of all transfers. + std::vector metas; + }; + + Buffer(const Buffer&) = delete; + Buffer& operator=(const Buffer&) = delete; + + Buffer(); + + virtual ~Buffer() { + } + + protected: + }; + + /*******************************************/ + /** */ + /** START OF DATA BUFFERS */ + /** */ + /*******************************************/ + + // A buffer that stores data on GPU. + // Usage of the data is determined by child classes. + class DataBuffer : public Buffer { + public: + DataBuffer(const DataBuffer&) = delete; + DataBuffer& operator=(const DataBuffer&) = delete; + + DataBuffer() = default; + + ~DataBuffer() override { + vmaDestroyBuffer(VkTools::allocator, managed.buffer, managed.allocation); + } + + protected: + using Buffer::Buffer; + + void setBuffer(const VkTools::ManagedBuffer& buffer) { managed = buffer; } + VkTools::ManagedBuffer get() const { return managed; } + const VkBuffer& getBuffer() const { return managed.buffer; } + + private: + VkTools::ManagedBuffer managed; + }; + + // A buffer visible to both GPU and CPU. + // Useful for uploading data to GPU for format conversions. + class StagingBuffer : public DataBuffer { + public: + StagingBuffer(const BulkCopyMeta& copyMeta); + + StagingBuffer(const StagingBuffer&) = delete; + StagingBuffer& operator=(const StagingBuffer&) = delete; + + void copy(const VkBuffer& target) const; + + private: + const VkDeviceSize dataSize; + }; + + // Root class of vertex buffers. + // Provides utilities for subclasses. + class VertexBuffer : public DataBuffer { + public: + VertexBuffer(const VertexBuffer&) = delete; + VertexBuffer& operator=(const VertexBuffer&) = delete; + + // Get attributes of vertexes in the buffer + // Location will start from "start" + // Binding will not be set + std::vector getAttrs(uint32_t start) const; + + // Draw these vertexes without a buffer per vertex. + static void draw(const VkCommandBuffer& buffer, uint32_t verts, uint32_t instances); + + protected: + friend class DynamicBuffer; + + explicit VertexBuffer(std::vector&& attrs) : DataBuffer(), attributes(attrs) {} + + // Initialize device memory and the managed buffer. + // indices and vertexes are put in the same buffer. + // if dynamic, the buffer will be host visible, this allows dynamic text. + // otherwise, the buffer is device local. + void create(VkDeviceSize totalSize, bool dynamic, bool indexes); + + const std::vector attributes; + }; + + /*******************************************/ + /** */ + /** END OF DATA BUFFERS */ + /** */ + /*******************************************/ + + // A simple plugin to allow the vertex buffer to be widened when reserved with a larger size. + class DynamicBuffer { + public: + DynamicBuffer(const DynamicBuffer&) = delete; + DynamicBuffer& operator=(const DynamicBuffer&) = delete; + + ~DynamicBuffer() = default; + + protected: + + DynamicBuffer(size_t size, bool hasIndices, VertexBuffer* buffer); + + // Reallocate the vertex buffer if the given pSize is larger than the available space + void resize(size_t pSize); + + VkDeviceSize bufferSize() const { return size; } + + private: + + const bool hasIndices; + VertexBuffer* vertexBuffer; + VkDeviceSize size = 0; + }; + + /*******************************************/ + /** */ + /** START OF PER-VERTEX BUFFERS */ + /** */ + /*******************************************/ + + // Root class of buffers that store per-vertex data. + // eg. shader data + class PerVertexBuffer : public VertexBuffer { + public: + // Interprets the layout of data in containers (vector, etc) + struct VertexDataMeta { + template + VertexDataMeta(const C& cont, int unitsPerMesh) : data(cont.data()), unitsPerMesh(unitsPerMesh), sizePerMesh(sizeof(cont[0]) * unitsPerMesh) {} + + template + VertexDataMeta(const C& cont) : VertexDataMeta(cont, static_cast(cont.size())) {} + + const void* data; + int unitsPerMesh; + size_t sizePerMesh; + }; + + // Interface for buffer data. + class BufferDataMeta { + public: + virtual ~BufferDataMeta() = default; + // Populate info and return a bulk copy meta for copying the data to device + virtual BulkCopyMeta prepareCopy(PerVertexBuffer* buffer) const = 0; + // Indicate whether the buffer contains index data too + virtual bool hasIndices() const = 0; + }; + + // Meshes do not share indices, with variable vertices. + class NoIndexBufferMeta : public BufferDataMeta { + public: + explicit NoIndexBufferMeta(std::vector&& perVertex) : perMeshVertices(perVertex) {} + + BulkCopyMeta prepareCopy(PerVertexBuffer* buffer) const override; + bool hasIndices() const override { return false; }; + private: + const std::vector perMeshVertices; + }; + + // Meshes share indices, with static vertices. + class SharedIndexMeta : public BufferDataMeta { + public: + SharedIndexMeta(int meshes, const VertexDataMeta& perVertex, const VertexDataMeta& sharedIndices) : meshes(meshes), + perMeshVertex(perVertex), + sharedIndices(sharedIndices) {} + BulkCopyMeta prepareCopy(PerVertexBuffer* buffer) const override; + bool hasIndices() const override { return true; }; + private: + const int meshes; + const VertexDataMeta perMeshVertex; + const VertexDataMeta sharedIndices; + }; + + // Meshes do not share indexes, with variable indices and vertices. + class NoShareMeta : public BufferDataMeta { + public: + struct PerMesh { + VertexDataMeta indices; + VertexDataMeta vertices; + }; + + explicit NoShareMeta(std::vector&& perMesh) : perMeshMeta(std::move(perMesh)) {} + + BulkCopyMeta prepareCopy(PerVertexBuffer* buffer) const override; + bool hasIndices() const override { return true; }; + private: + const std::vector perMeshMeta; + }; + + PerVertexBuffer(const PerVertexBuffer&) = delete; + PerVertexBuffer& operator=(const PerVertexBuffer&) = delete; + + // Render mesh a given number of times, into a recording buffer. + void draw(const VkCommandBuffer& buffer, uint32_t bind, int index, uint32_t instances) const; + + protected: + using VertexBuffer::VertexBuffer; + + // Stores vertex data for buffers without indices + struct MeshDataNoIndex { + struct Info { + uint32_t vertexCount; + VkDeviceSize vertexStart; + }; + + std::vector info; + }; + + // Stores vertex and index data for buffers with both + struct MeshDataIndex { + struct Info { + uint32_t indexCount; + VkDeviceSize indexStart; + VkDeviceSize vertexStart; + }; + + std::vector info; + }; + + std::variant* getInfo() { return &meshDataInfo; } + + private: + + std::variant meshDataInfo; + }; + + // Stores static data for one-time upload. + class StaticPerVertexBuffer : public PerVertexBuffer { + public: + StaticPerVertexBuffer(const BufferDataMeta& info, std::vector&& attrs); + + StaticPerVertexBuffer(const StaticPerVertexBuffer&) = delete; + StaticPerVertexBuffer& operator=(const StaticPerVertexBuffer&) = delete; + }; + + // Stores host-visible data that can be reallocated. + class DynamicPerVertexBuffer : public PerVertexBuffer, public DynamicBuffer { + public: + DynamicPerVertexBuffer(size_t size, std::vector&& attrs) : PerVertexBuffer(std::move(attrs)), DynamicBuffer(size, true, this) {} + + DynamicPerVertexBuffer(const DynamicPerVertexBuffer&) = delete; + DynamicPerVertexBuffer& operator=(const DynamicPerVertexBuffer&) = delete; + + void copyToDevice(const BufferDataMeta& meta); + }; + + /*******************************************/ + /** */ + /** END OF PER-VERTEX BUFFERS */ + /** */ + /*******************************************/ + + // Root class of buffers that store vertex data per instance of a mesh. + class PerInstanceVertexBuffer : public VertexBuffer { + public: + PerInstanceVertexBuffer(const PerInstanceVertexBuffer&) = delete; + PerInstanceVertexBuffer& operator=(const PerInstanceVertexBuffer&) = delete; + + void bind(const VkCommandBuffer& commands, uint32_t bindPoint, int offset) const; + + uint32_t getSize() const { return sizePerInstance; } + + protected: + PerInstanceVertexBuffer(uint32_t size, std::vector&& attrs) : VertexBuffer(std::move(attrs)), sizePerInstance(size) {} + private: + const uint32_t sizePerInstance; + }; + + // Stores vertices that are static per instance of the mesh. + class StaticPerInstanceBuffer : public PerInstanceVertexBuffer { + public: + StaticPerInstanceBuffer(uint32_t size, const void* data, uint32_t instances, std::vector&& attrs); + + template + StaticPerInstanceBuffer(const C& cont, std::vector&& attrs) : StaticPerInstanceBuffer(sizeof(cont[0]), cont.data(), CONTAINER_SIZE(cont), std::move(attrs)) {} + + StaticPerInstanceBuffer(const StaticPerInstanceBuffer&) = delete; + StaticPerInstanceBuffer& operator=(const StaticPerInstanceBuffer&) = delete; + }; + + // Stores vertices of meshes that are dynamic (ie. text, shifting meshes + class DynamicPerInstanceBuffer : public PerInstanceVertexBuffer, public DynamicBuffer { + public: + DynamicPerInstanceBuffer(uint32_t size, size_t maxInstances, std::vector&& attrs) : PerInstanceVertexBuffer(size, std::move(attrs)), DynamicBuffer(size * maxInstances, false, this) {} + + DynamicPerInstanceBuffer(const DynamicPerInstanceBuffer&) = delete; + DynamicPerInstanceBuffer& operator=(const DynamicPerInstanceBuffer*) = delete; + + void copyToDevice(const void* data, uint32_t instances); + + template + void copyToDevice(const C& cont) { + copyToDevice(cont.data(), CONTAINER_SIZE(cont)); + } + }; + + /*******************************************/ + /** */ + /** END OF PER-INSTANCE BUFFERS */ + /** */ + /*******************************************/ + + // Holds uniform data on host and device. + // Supports superallocating (allocating more than one "set" of uniforms at once) + // Data is stored on host and device simultaneously, so set the host data and flush it to the device. + class UniformBuffer : public DataBuffer { + public: + UniformBuffer(size_t chunkSize, int chunks); + + UniformBuffer(const UniformBuffer&) = delete; + UniformBuffer& operator=(const UniformBuffer&) = delete; + + ~UniformBuffer() override { delete data; } + + // Whether this buffer holds a single chunk (is not superallocated). + // Simplifies certain algorithms significantly if you know this beforehand + bool isSingle() const { return numChunks == 1; } + + // Get the data in the buffer, casted to the given type + template + DataType* getData(int index) const { + checkIndex(index); + return reinterpret_cast(data + chunkSize * index); + } + + // Upload (flush) the uniform to the GPU + void upload(int index) const; + void upload(int index, VkDeviceSize dataSize, VkDeviceSize start) const; + + static VkDescriptorType getDescriptorType() { return VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER; } + + VkDescriptorBufferInfo getDescriptorInfo(int index) const; + + private: + void checkIndex(int index) const; + + char* data; + const size_t chunkSize; + const int numChunks; + + size_t chunkLength; + }; + + // A small, highly efficient buffer much like the Uniform Buffer. + class PushConstant { + public: + // Size must be < 128. + PushConstant(size_t size, int numFrames); + + PushConstant(const PushConstant&) = delete; + PushConstant& operator=(const PushConstant&) = delete; + + ~PushConstant() { delete[] data; } + + // Whether this buffer holds a single chunk (is not superallocated). + // Simplifies certain algorithms significantly if you know this beforehand + bool isSingle() const { return numFrames == 1; } + + uint32_t getSize() const { return sizePerFrame; } + + // Get the data in the buffer, casted to the given type + template + DataType* getData(int frame) const { + checkIndex(frame); + return reinterpret_cast(data + (sizePerFrame * frame)); + } + + VkPushConstantRange makeRange(VkShaderStageFlags stage) { + return VkPushConstantRange { stage, 0, sizePerFrame }; + } + + // Upload (flush) the uniform to the GPU + void upload(const VkCommandBuffer& commands, const VkPipelineLayout& pipelineLayout, int frame, uint32_t offset, VkShaderStageFlags stage) const; + + + private: + void checkIndex(int index) const; + + char* data; + const uint32_t sizePerFrame; + const int numFrames; + }; + +} \ No newline at end of file diff --git a/projs/shadow/shadow-engine/shadow-renderer/inc/vlkx/vulkan/abstraction/Commands.h b/projs/shadow/shadow-engine/shadow-renderer/inc/vlkx/vulkan/abstraction/Commands.h new file mode 100644 index 0000000..c426c09 --- /dev/null +++ b/projs/shadow/shadow-engine/shadow-renderer/inc/vlkx/vulkan/abstraction/Commands.h @@ -0,0 +1,87 @@ +#pragma once + +#include +#include "Queue.h" +#include "vlkx/vulkan/VulkanDevice.h" +#include +#include + +namespace vlkx { + + // Root class of VkCommandBuffer wrappers. + class CommandBuffer { + public: + using Command = std::function; + + CommandBuffer(const CommandBuffer&) = delete; + CommandBuffer& operator=(const CommandBuffer&) = delete; + + virtual ~CommandBuffer() { + vkDestroyCommandPool(dev->logical, pool, nullptr); + } + protected: + CommandBuffer(); + + void setPool(const VkCommandPool& newPool) { pool = newPool; } + VulkanDevice* dev; + private: + VkCommandPool pool; + }; + + // A command buffer that will be immediately executed. + class ImmediateCommand : public CommandBuffer { + public: + ImmediateCommand(Queue queue); + + ImmediateCommand(const ImmediateCommand&) = delete; + ImmediateCommand& operator=(const ImmediateCommand&) = delete; + + void run(const Command& cmd); + + private: + const Queue queue; + VkCommandBuffer commands; + }; + + // A command buffer that will be reused every frame. + class RenderCommand : public CommandBuffer { + public: + using Command = std::function; + using Update = std::function; + + ~RenderCommand() override { + // Destroy our own data + vkDestroySemaphore(dev->logical, renderDoneSem, nullptr); + vkDestroySemaphore(dev->logical, newImageSem, nullptr); + + for (size_t i = 0; i < 2; i++) { + vkDestroyFence(dev->logical, inFlight[i], nullptr); + } + } + + RenderCommand(int frames); + + RenderCommand(const RenderCommand&) = delete; + RenderCommand& operator=(const RenderCommand&) = delete; + + uint32_t getFrame() { return imageIndex; } + + void nextFrame() { imageIndex = (imageIndex + 1) % 2; } + + std::optional execute(int frame, const VkSwapchainKHR& swapchain, const Update& update, const Command& cmd); + // Renders a single frame out, no semaphores or fences. + std::optional executeSimple(int frame, const Update& update, const Command& cmd); + + private: + std::vector commands; + // Raised when a new image is available + VkSemaphore newImageSem; + // Raised when a render is finished + VkSemaphore renderDoneSem; + // Stores fences for frames that are currently "in flight". + std::vector inFlight; + + // The index of the texture that is currently being used by the GPU. + uint32_t imageIndex = 0; + }; +} \ No newline at end of file diff --git a/projs/shadow/shadow-engine/shadow-renderer/inc/vlkx/vulkan/abstraction/Descriptor.h b/projs/shadow/shadow-engine/shadow-renderer/inc/vlkx/vulkan/abstraction/Descriptor.h new file mode 100644 index 0000000..336f9c6 --- /dev/null +++ b/projs/shadow/shadow-engine/shadow-renderer/inc/vlkx/vulkan/abstraction/Descriptor.h @@ -0,0 +1,71 @@ +#pragma once + +#include +#include +#include +#include +#include + +namespace vlkx { + + class Descriptor { + public: + using TextureType = vlkxtemp::ModelLoader::TextureType; + using BufferInfos = std::map>; + using ImageInfos = std::map>; + + struct Meta { + struct Binding { + uint32_t bindPoint; + uint32_t length; + }; + + VkDescriptorType type; + VkShaderStageFlags stage; + std::vector bindings; + }; + + Descriptor(const Descriptor&) = delete; + Descriptor& operator=(const Descriptor&) = delete; + + virtual ~Descriptor() { + vkDestroyDescriptorSetLayout(VulkanModule::getInstance()->getDevice()->logical, layout, nullptr); + } + + const VkDescriptorSetLayout& getLayout() const { return layout; } + + protected: + explicit Descriptor() = default; + + void setLayout(const VkDescriptorSetLayout& newLayout) { layout = newLayout; } + + private: + VkDescriptorSetLayout layout; + }; + + class StaticDescriptor : public Descriptor { + public: + + StaticDescriptor(std::vector metas); + StaticDescriptor(const StaticDescriptor&) = delete; + StaticDescriptor& operator=(const StaticDescriptor&) = delete; + + ~StaticDescriptor() override { + vkDestroyDescriptorPool(VulkanModule::getInstance()->getDevice()->logical, pool, nullptr); + } + + const StaticDescriptor& buffers(VkDescriptorType type, const BufferInfos& infos) const; + const StaticDescriptor& images(VkDescriptorType type, const ImageInfos& infos) const; + + void bind(const VkCommandBuffer& commands, const VkPipelineLayout& layout, VkPipelineBindPoint bindPoint) const; + + private: + + const StaticDescriptor& updateSet(const std::vector& write) const; + + VkDescriptorPool pool; + VkDescriptorSet set; + }; + + // TODO: dynamic sets +} \ No newline at end of file diff --git a/projs/shadow/shadow-engine/shadow-renderer/inc/vlkx/vulkan/abstraction/Image.h b/projs/shadow/shadow-engine/shadow-renderer/inc/vlkx/vulkan/abstraction/Image.h new file mode 100644 index 0000000..b78700b --- /dev/null +++ b/projs/shadow/shadow-engine/shadow-renderer/inc/vlkx/vulkan/abstraction/Image.h @@ -0,0 +1,331 @@ +#pragma once + +#include "vlkx/vulkan/Tools.h" +#include "ImageUsage.h" +#include +#include "Buffer.h" +#include + +#include + +namespace vlkx { + + // Describes an image without initializing or storing any heavy data. + class ImageDescriptor { + public: + enum class Type { Single, Cubemap }; + struct Dimension { + uint32_t width; + uint32_t height; + uint32_t channels; + + VkExtent2D getExtent() const { return { width, height }; } + size_t getSize() const { return width * height * channels; } + }; + + Type getType() const { return type; } + VkExtent2D getExtent() const { return dimensions.getExtent(); } + uint32_t getWidth() const { return dimensions.width; } + uint32_t getHeight() const { return dimensions.height; } + uint32_t getChannels() const { return dimensions.channels; } + + std::vector getData() const { + if (type == Type::Single) return { (void*) data }; + std::vector dataPtrs; + dataPtrs.reserve(6); + + size_t offset = 0; + for (size_t i = 0; i < 6; i++) { + dataPtrs.emplace_back((char*) data + offset); + offset += dimensions.getSize(); + } + + return dataPtrs; + } + + int getLayers() const { return type == Type::Single ? 1 : 6; } + + ImageDescriptor(Type t, const Dimension& d, const void* ptr) : type(t), dimensions(d), data(ptr) {} + + private: + Type type; + Dimension dimensions; + const void* data; + + }; + + // A staging buffer specialized for uploading images. + class ImageStagingBuffer : public StagingBuffer { + public: + using StagingBuffer::StagingBuffer; + + ImageStagingBuffer(const ImageStagingBuffer&) = delete; + ImageStagingBuffer& operator=(const ImageStagingBuffer&) = delete; + + void copy(const VkImage& target, const VkExtent3D& extent, uint32_t layers) const; + }; + + // Root class that stores image data on GPU buffers + class ImageBuffer : public Buffer { + public: + ImageBuffer(const ImageBuffer&) = delete; + ImageBuffer& operator=(const ImageBuffer&) = delete; + + ~ImageBuffer() override { + vmaDestroyImage(VkTools::allocator, image.image, image.allocation); + } + + const VkTools::ManagedImage& get() const { return image; } + const VkImage& getImage() const { return image.image; } + + protected: + using Buffer::Buffer; + + void setImage(const VkTools::ManagedImage newImg) { image = newImg; } + + private: + VkTools::ManagedImage image; + + }; + + // Base class of all images; stores the common data. + class Image { + public: + Image(const Image&) = delete; + Image& operator=(const Image&) = delete; + + virtual ~Image() { + vkDestroyImageView(dev->logical, view, nullptr); + } + + static VkDescriptorType getSampleType() { return VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER; } + static VkDescriptorType getLinearType() { return VK_DESCRIPTOR_TYPE_STORAGE_IMAGE; } + + static ImageDescriptor loadSingleFromDisk(std::string path, bool flipY); + // The following are left unimplemented intentionally. + //static ImageDescriptor loadSingleFromVFS(std::string path, bool flipY); + static ImageDescriptor loadCubeFromDisk(const std::string& directory, const std::array& files, bool flipY); + //static ImageDescriptor loadCubeFromVFS(std::string directory, const std::array& files, bool flipY); + + virtual ImageUsage getUsage() const { return ImageUsage {}; } + + const VkTools::ManagedImage& operator*() const { return get(); } + + virtual const VkTools::ManagedImage& get() const = 0; + virtual const VkImage& getImage() const = 0; + + const VkImageView& getView() const { return view; } + const VkExtent2D& getExtent() const { return extent; } + VkFormat getFormat() const { return format; } + virtual VkSampleCountFlagBits getSamples() const { return VK_SAMPLE_COUNT_1_BIT; } + + + protected: + Image(const VkExtent2D& ext, VkFormat form); + + void setView(const VkImageView& imgView) { view = imgView; } + + VulkanDevice* dev; + VkImageView view; + VkExtent2D extent; + VkFormat format; + VkSampleCountFlagBits sampleCount; + }; + + // Configures image sampling in a sensible and extensible way + class ImageSampler { + public: + struct Config { + explicit Config(VkFilter filter = VK_FILTER_LINEAR, VkSamplerAddressMode mode = VK_SAMPLER_ADDRESS_MODE_REPEAT) : filter(filter), mode(mode) {} + + VkFilter filter; + VkSamplerAddressMode mode; + }; + + ImageSampler(int mipLevels, const Config& config); + + ImageSampler(const ImageSampler&) = delete; + ImageSampler& operator=(const ImageSampler&) = delete; + + ~ImageSampler() { + vkDestroySampler(dev->logical, sampler, nullptr); + } + + const VkSampler& operator*() const { return sampler; } + + private: + VkSampler sampler; + VulkanDevice* dev; + }; + + // Root of images which can be sampled. + class SamplableImage { + public: + virtual ~SamplableImage() = default; + + // Return a VkDescriptorImageInfo we can use to update sets. + virtual VkDescriptorImageInfo getInfo(VkImageLayout layout) const = 0; + VkDescriptorImageInfo getInfoForSampling() const { return getInfo(VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL); } + VkDescriptorImageInfo getInfoForLinear() const { return getInfo(VK_IMAGE_LAYOUT_GENERAL); } + }; + + // A samplable image that lives on the GPU, with optional mipmapping. + // Use a RefCountedTexture when loading from files. + class TextureImage : public Image, public SamplableImage { + public: + // Image metadata + struct Meta { + VkExtent2D getExtent() const { return { width, height }; } + VkExtent3D get3DExtent() const { return { width, height, channels }; } + + Buffer::BulkCopyMeta getCopyMeta() const; + + std::vector data; + std::vector usages; + VkFormat format; + uint32_t width; + uint32_t height; + uint32_t channels; + }; + + TextureImage(bool mipmapping, const ImageSampler::Config& samplerConfig, const Meta& meta); + TextureImage(bool mipmapping, const ImageDescriptor& image, const std::vector& usages, const ImageSampler::Config& config); + + TextureImage(const TextureImage&) = delete; + TextureImage& operator=(const TextureImage&) = delete; + + const VkTools::ManagedImage& get() const override { return buffer.get(); } + const VkImage& getImage() const override { return buffer.getImage(); } + + VkDescriptorImageInfo getInfo(VkImageLayout layout) const override { + return { *sampler, getView(), layout }; + } + // Textures are sampled in fragment shaders. + ImageUsage getUsage() const override { + return ImageUsage::sampledFragment(); + } + private: + class TextureBuffer : public ImageBuffer { + public: + TextureBuffer(bool mipmaps, const Meta& meta); + + TextureBuffer(const TextureBuffer&) = delete; + TextureBuffer& operator=(const TextureBuffer&) = delete; + + int getMipping() const { return mipLevels; } + private: + int mipLevels = 1; + }; + + const TextureBuffer buffer; + const ImageSampler sampler; + }; + + // A texture image that lives on GPU that is reference counted. + // This allows it to be reused multiple times without reading the file in more than once. + // It also allows the texture to be destructed automatically once nothing in the scene uses it, for eg. level changes. + class RefCountedTexture : public SamplableImage { + public: + // Cubemaps are simply 6 textures, so we include them here for easy instantiation. + struct CubemapLocation { + std::string directory; + std::array files; + }; + + // Reference Counting works on both individual files and cubemaps, so we put them together. + using ImageLocation = std::variant; + + RefCountedTexture(const ImageLocation& location, std::vector usages, const ImageSampler::Config& config) + : texture(get(location, std::move(usages), config)) {} + + RefCountedTexture(RefCountedTexture&&) noexcept = default; + RefCountedTexture& operator=(RefCountedTexture&&) noexcept = default; + + VkDescriptorImageInfo getInfo(VkImageLayout layout) const override { + return texture->getInfo(layout); + } + + const Image* operator->() const { return texture.operator->(); } + + private: + using ReferenceCounter = shadowutil::RefCounter; + // Get or load the specified image. + static ReferenceCounter get(const ImageLocation& location, const std::vector& usages, const ImageSampler::Config& config); + + ReferenceCounter texture; + }; + + // TODO: unowned, offscreen images. + + // Image that can be used as a depth / stencil buffer attachment. + class DepthStencilImage : public Image { + public: + DepthStencilImage(const DepthStencilImage&) = delete; + DepthStencilImage& operator=(const DepthStencilImage&) = delete; + + DepthStencilImage(const VkExtent2D& extent); + + const VkTools::ManagedImage& get() const override { return buffer.get(); } + const VkImage& getImage() const override { return buffer.getImage(); } + + private: + class DepthStencilBuffer : public ImageBuffer { + public: + DepthStencilBuffer(const VkExtent2D& extent, VkFormat format); + DepthStencilBuffer(const DepthStencilBuffer&) = delete; + DepthStencilBuffer& operator=(const DepthStencilBuffer&) = delete; + }; + + const DepthStencilBuffer buffer; + }; + + // Image that references an existing image on the swapchain + class SwapchainImage : public Image { + public: + SwapchainImage(const SwapchainImage&) = delete; + SwapchainImage& operator=(const SwapchainImage&) = delete; + + SwapchainImage(const VkImage& image, const VkExtent2D& extent, VkFormat format); + + const VkTools::ManagedImage& get() const override { return managed; } + const VkImage& getImage() const override { return image; } + + private: + VkImage image; + VkTools::ManagedImage managed; + }; + + class MultisampleImage : public Image { + public: + enum class Mode { + MostEfficient, + Highest + }; + + static std::unique_ptr createColor(const Image& targetImage, Mode mode); + static std::unique_ptr createDepthStencilMS(const VkExtent2D& extent, Mode mode); + static std::unique_ptr createDepthStencil(VkExtent2D& extent, std::optional mode); + + const VkTools::ManagedImage& get() const override { return buffer.get(); } + const VkImage& getImage() const override { return buffer.getImage(); } + + VkSampleCountFlagBits getSamples() const override { return samples; } + private: + class MultisampleBuffer : public ImageBuffer { + public: + enum class Type { Color, DepthStencil }; + + MultisampleBuffer(Type type, const VkExtent2D& extent, VkFormat format, VkSampleCountFlagBits samples); + MultisampleBuffer(const MultisampleBuffer&) = delete; + MultisampleBuffer& operator=(const MultisampleBuffer&) = delete; + }; + + MultisampleImage(const VkExtent2D& extent, VkFormat format, Mode mode, MultisampleBuffer::Type type); + + VkSampleCountFlagBits chooseSamples(Mode mode); + + const VkSampleCountFlagBits samples; + const MultisampleBuffer buffer; + }; + +} \ No newline at end of file diff --git a/projs/shadow/shadow-engine/shadow-renderer/inc/vlkx/vulkan/abstraction/ImageUsage.h b/projs/shadow/shadow-engine/shadow-renderer/inc/vlkx/vulkan/abstraction/ImageUsage.h new file mode 100644 index 0000000..34e6c3a --- /dev/null +++ b/projs/shadow/shadow-engine/shadow-renderer/inc/vlkx/vulkan/abstraction/ImageUsage.h @@ -0,0 +1,297 @@ +#pragma once + +#include +#include +#include +#include +#include + +namespace vlkx { + /** + * Describes how an image (collection of color data) will be used in the GPU. + * Has three parts; an overall description, access methods, and location of access. + * Use the static methods to create an instance, or the blank initializer and set fields as required. + */ + class ImageUsage { + public: + enum class Type { + DontCare, // Image is unused + RenderTarget, // Color attachment is used + DepthStencil, // Depth / Stencil buffer is used + Multisample, // Resolves to a multisampled image + Presentation, // Framebuffer for presentation + LinearAccess, // Host-Readable + InputAttachment, // Only what we write, is read. + Sampled, // Image is sampled (ie. texture) + Transfer // Image is used as an intermediate for transfer. + }; + + enum class Access { + DontCare, // Image is unused + ReadOnly, // Read Only access. + WriteOnly, // Write Only access. + ReadWrite, // Read/Write access. + }; + + enum class Location { + DontCare, // Image is unused + Host, // Image only exists on the host and will be transferred. + VertexShader, // Image is only used in the VertexAll Shader. + FragmentShader, // Image is only used in the Fragment Shader. + ComputeShader, // Image is only used in a Compute Shader. + Other, // Reserved. + }; + + // Sampled in a fragment shader. Read-Only. + static ImageUsage sampledFragment() { return { Type::Sampled, Access::ReadOnly, Location::FragmentShader }; }; + // Used as a render target (a render pass will output to this image). Read/Write. + static ImageUsage renderTarget(int loc) { return { Type::RenderTarget, Access::ReadWrite, Location::Other, loc}; }; + // Resolves to a multisampled image. Write-Only. + static ImageUsage multisample() { return { Type::Multisample, Access::WriteOnly, Location::Other }; }; + // A depth or stencil buffer. Access is given, but must not be DontCare. + static ImageUsage depthStencil(Access acc) { return { Type::DepthStencil, acc, Location::Other}; }; + // Used to show to the user. Write-Only. + static ImageUsage presentation() { return { Type::Presentation, Access::ReadOnly, Location::Other }; }; + // Input attachment for a fragment shader. Usually a texture. Read-Only. + static ImageUsage input() { return { Type::InputAttachment, Access::ReadOnly, Location::FragmentShader }; }; + // Linearly accessed image. For a compute shader. Access is given, but must not be DontCare. + static ImageUsage compute(Access acc) { return { Type::LinearAccess, acc, Location::ComputeShader }; }; + + explicit ImageUsage() : ImageUsage(Type::DontCare, Access::DontCare, Location::DontCare) {} + + bool operator==(const ImageUsage& other) const { + return type == other.type && access == other.access && location == other.location; + } + + VkImageUsageFlagBits getUsageFlags() const { + switch (type) { + case Type::DontCare: throw std::runtime_error("No usage for type DontCare"); + case Type::RenderTarget: + case Type::Multisample: + case Type::Presentation: + return VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT; + case Type::DepthStencil: + return VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT; + case Type::LinearAccess: + return VK_IMAGE_USAGE_STORAGE_BIT; + case Type::Sampled: + return VK_IMAGE_USAGE_SAMPLED_BIT; + case Type::Transfer: + switch (access) { + case Access::DontCare: throw std::runtime_error("No access type specified for transfer usage."); + case Access::ReadOnly: return VK_IMAGE_USAGE_TRANSFER_SRC_BIT; + case Access::WriteOnly: return VK_IMAGE_USAGE_TRANSFER_DST_BIT; + case Access::ReadWrite: throw std::runtime_error("ReadWrite access type for Transfer usage is invalid."); + } + } + } + + static VkImageUsageFlags getFlagsForUsage(const std::vector& usages) { + auto flags = 0; + for (const auto& usage : usages) { + if (usage.type != Type::DontCare) + flags |= usage.getUsageFlags(); + } + + return static_cast(flags); + } + + VkImageLayout getLayout() const { + switch (type) { + case Type::DontCare: return VK_IMAGE_LAYOUT_UNDEFINED; + case Type::RenderTarget: + case Type::Multisample: + return VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL; + case Type::DepthStencil: return VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL; + case Type::Presentation: return VK_IMAGE_LAYOUT_PRESENT_SRC_KHR; + case Type::LinearAccess: return VK_IMAGE_LAYOUT_GENERAL; + case Type::Sampled: return VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL; + case Type::Transfer: return access == Access::ReadOnly ? VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL : VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL; + case Type::InputAttachment: + break; + } + } + + VkPipelineStageFlags getStage() const { + switch (type) { + case Type::DontCare: return VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT; + case Type::RenderTarget: + case Type::Multisample: + case Type::Presentation: + return VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT; + case Type::DepthStencil: + return VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT | VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT; + case Type::LinearAccess: + case Type::Sampled: + switch (location) { + case Location::Host: return VK_PIPELINE_STAGE_HOST_BIT; + case Location::FragmentShader: return VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT; + case Location::ComputeShader: return VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT; + + case Location::VertexShader: + case Location::Other: + throw std::runtime_error("Linear or sampled attachments must not be used in VertexAll or Other stages."); + case Location::DontCare: throw std::runtime_error("Linear or sampled attachments must have an access."); + } + + case Type::Transfer: + return VK_PIPELINE_STAGE_TRANSFER_BIT; + } + } + + Access getAccess() const { return access; } + + Type getType() const { return type; } + + VkAccessFlags getAccessFlags() const { + switch (type) { + case Type::DontCare: return VK_ACCESS_NONE_KHR; + case Type::RenderTarget: return getReadOrWrite(VK_ACCESS_COLOR_ATTACHMENT_READ_BIT, VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT); + case Type::DepthStencil: return getReadOrWrite(VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_READ_BIT, VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT); + case Type::Multisample: return VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT; + case Type::Presentation: return 0; + case Type::LinearAccess: + case Type::Sampled: + return location == Location::Host ? getReadOrWrite(VK_ACCESS_HOST_READ_BIT, VK_ACCESS_HOST_WRITE_BIT) : getReadOrWrite(VK_ACCESS_SHADER_READ_BIT, VK_ACCESS_SHADER_WRITE_BIT); + case Type::Transfer: + return getReadOrWrite(VK_ACCESS_TRANSFER_READ_BIT, VK_ACCESS_TRANSFER_WRITE_BIT); + case Type::InputAttachment: + return 0; + } + }; + + private: + + ImageUsage(Type t, Access a, Location l, std::optional att = std::nullopt) + : type(t), access(a), location(l), attachment(att) {} + + Type type; + Access access; + Location location; + std::optional attachment; + + inline VkAccessFlags getReadOrWrite(VkAccessFlags read, VkAccessFlags write) const { + VkAccessFlags flag = 0; + if (access == Access::ReadOnly || access == Access::ReadWrite) + flag |= read; + if (access == Access::WriteOnly || access == Access::ReadWrite) + flag |= write; + return flag; + }; + }; + + /** + * Describes how a single image will be used in each stage of a render pass. + * Allows a single image to be written to during one pass, read in a second, and presented in the final, and tracked. + * Helps figure out the optimizations that Vulkan can do to this image and the render passes that use it. + */ + class UsageTracker { + public: + + explicit UsageTracker(const ImageUsage& initial) : initialUsage(initial) {} + explicit UsageTracker() = default; + + UsageTracker(UsageTracker&&) noexcept = default; + UsageTracker& operator=(UsageTracker&&) noexcept = default; + + // Fluent API; chain calls in a builder pattern. + #define fluent UsageTracker& + + fluent add(int pass, const ImageUsage& usage) { + usageAtSubpass.insert( { pass, usage} ); + + return *this; + } + + fluent add(int start, int end, const ImageUsage& usage) { + for (int subpass = start; subpass <= end; ++subpass) + add(subpass, usage); + + return *this; + } + + fluent addMultisample(int pass, std::string_view name) { + multisamples.insert( { pass, std::string(name) } ); + return add(pass, ImageUsage::multisample()); + } + + fluent setFinal(const ImageUsage& usage) { + finalUsage = usage; + return *this; + } + + [[nodiscard]] std::vector getUsages() const { + size_t count = usageAtSubpass.size() + (finalUsage.has_value() ? 1 : 0) + 1; + + std::vector usages; + usages.reserve(count); + usages.emplace_back(initialUsage); + + for(const auto& pair : usageAtSubpass) + usages.emplace_back(pair.second); + + if (finalUsage.has_value()) + usages.emplace_back(finalUsage.value()); + + return usages; + } + + [[nodiscard]] const std::map& getUsageMap() const { + return usageAtSubpass; + } + + ImageUsage& getInitialUsage() { return initialUsage; } + std::optional getFinalUsage() { return finalUsage; } + + + + private: + + std::map usageAtSubpass; + ImageUsage initialUsage; + std::optional finalUsage; + std::map multisamples; + }; + + /** + * A simple wrapper that allows tracking the current usage of multiple images. + */ + class MultiImageTracker { + public: + MultiImageTracker() = default; + + MultiImageTracker(const MultiImageTracker&) = delete; + MultiImageTracker& operator=(const MultiImageTracker&) = delete; + + // Fluent API; chain calls in a builder pattern + #undef fluent + #define fluent MultiImageTracker& + + fluent track(std::string&& name, const ImageUsage& usage) { + images.insert( { std::move(name), usage } ); + return *this; + } + + fluent track(const std::string& name, const ImageUsage& usage) { + return track(std::string(name), usage); + } + + fluent update(const std::string& name, const ImageUsage& usage) { + auto iter = images.find(name); + iter->second = usage; + return *this; + } + + [[nodiscard]] bool isTracking(const std::string& image) const { + return images.contains(image); + } + + [[nodiscard]] const ImageUsage& get(const std::string& image) const { + return images.at(image); + } + + private: + std::map images; + + }; +} \ No newline at end of file diff --git a/projs/shadow/shadow-engine/shadow-renderer/inc/vlkx/vulkan/abstraction/Queue.h b/projs/shadow/shadow-engine/shadow-renderer/inc/vlkx/vulkan/abstraction/Queue.h new file mode 100644 index 0000000..ae64d40 --- /dev/null +++ b/projs/shadow/shadow-engine/shadow-renderer/inc/vlkx/vulkan/abstraction/Queue.h @@ -0,0 +1,8 @@ +#pragma once + +namespace vlkx { + struct Queue { + VkQueue queue; + int queueIndex; + }; +} \ No newline at end of file diff --git a/projs/shadow/shadow-engine/shadow-renderer/inc/vulkan/vk_mem_alloc.h b/projs/shadow/shadow-engine/shadow-renderer/inc/vulkan/vk_mem_alloc.h index c8a9866..3918862 100644 --- a/projs/shadow/shadow-engine/shadow-renderer/inc/vulkan/vk_mem_alloc.h +++ b/projs/shadow/shadow-engine/shadow-renderer/inc/vulkan/vk_mem_alloc.h @@ -5489,7 +5489,6 @@ public: // Posts next part of an open string. void ContinueString(const char* pStr); // Posts next part of an open string. The number is converted to decimal characters. - void ContinueString(uint32_t n); void ContinueString(uint64_t n); // Posts next part of an open string. Pointer value is converted to characters // using "%p" formatting - shown as hexadecimal number, e.g.: 000000081276Ad00 @@ -5498,7 +5497,6 @@ public: void EndString(const char* pStr = VMA_NULL); // Writes a number value. - void WriteNumber(uint32_t n); void WriteNumber(uint64_t n); // Writes a boolean value - false or true. void WriteBool(bool b); @@ -5654,12 +5652,6 @@ void VmaJsonWriter::ContinueString(const char* pStr) } } -void VmaJsonWriter::ContinueString(uint32_t n) -{ - VMA_ASSERT(m_InsideString); - m_SB.AddNumber(n); -} - void VmaJsonWriter::ContinueString(uint64_t n) { VMA_ASSERT(m_InsideString); @@ -5683,13 +5675,6 @@ void VmaJsonWriter::EndString(const char* pStr) m_InsideString = false; } -void VmaJsonWriter::WriteNumber(uint32_t n) -{ - VMA_ASSERT(!m_InsideString); - BeginValue(false); - m_SB.AddNumber(n); -} - void VmaJsonWriter::WriteNumber(uint64_t n) { VMA_ASSERT(!m_InsideString); diff --git a/projs/shadow/shadow-engine/shadow-renderer/src/render/Camera.cpp b/projs/shadow/shadow-engine/shadow-renderer/src/render/Camera.cpp index 2776636..a4544e4 100644 --- a/projs/shadow/shadow-engine/shadow-renderer/src/render/Camera.cpp +++ b/projs/shadow/shadow-engine/shadow-renderer/src/render/Camera.cpp @@ -1,20 +1,129 @@ #include -void Camera::init(float fov, float width, float height, float near, float far) { +using namespace vlkx; - // Initialise members - position = glm::vec3(0, 0, 4); - - glm::vec3 cameraFront = glm::vec3(0, 0, 0); - glm::vec3 cameraUp = glm::vec3(0, 1, 0); - - viewMatrix = glm::mat4(1); - projectionMatrix = glm::mat4(1); - - projectionMatrix = glm::perspective(fov, width / height, near, far); - viewMatrix = glm::lookAt(position, cameraFront, cameraUp); +Camera& Camera::move(const glm::vec3 &delta) { + position += delta; + return *this; } -void Camera::setPosition(glm::vec3 newPosition) { - position = newPosition; -} \ No newline at end of file +Camera &Camera::setPos(const glm::vec3 &pos) { + position = pos; + return *this; +} + +Camera &Camera::up(const glm::vec3 &up) { + upVector = glm::normalize(up); + return *this; +} + +Camera &Camera::forward(const glm::vec3 &forward) { + frontVector = glm::normalize(forward); + rightVector = glm::normalize(glm::cross(frontVector, upVector)); + return *this; +} + +glm::mat4 Camera::getViewMatrix() const { + return glm::lookAt(position, position + frontVector, upVector); +} + +PerspectiveCamera &PerspectiveCamera::fieldOfView(float newFov) { + fov = newFov; + return *this; +} + +PerspectiveCamera::RT PerspectiveCamera::getRT() const { + const glm::vec3 upVec = glm::normalize(glm::cross(getRight(), getForward())); + const float fovTan = glm::tan(glm::radians(fov)); + return { upVec * fovTan, getForward(), getRight() * fovTan * aspectRatio }; +} + +glm::mat4 PerspectiveCamera::getProjMatrix() const { + glm::mat4 proj = glm::perspective(glm::radians(fov), aspectRatio, nearPlane, farPlane); + proj[1][1] = -proj[1][1]; + return proj; +} + +OrthographicCamera &OrthographicCamera::setWidth(float vWidth) { + width = vWidth; + return *this; +} + +glm::mat4 OrthographicCamera::getProjMatrix() const { + const float height = width / aspectRatio; + const auto halfSize = glm::vec2 { width, height } / 2.0f; + return glm::ortho(-halfSize.x, halfSize.x, -halfSize.y, halfSize.y, nearPlane, farPlane); +} + +template +void UserCamera::setInternal(std::function op) { + op(camera.get()); + reset(); +} + +template +void UserCamera::move(double x, double y) { + if (!isActive) return; + + const auto offsetX = static_cast(x * config.turnSpeed); + const auto offsetY = static_cast(y * config.turnSpeed); + + pitch = glm::clamp(pitch - offsetY, glm::radians(-89.9f), glm::radians(89.9f)); + yaw = glm::mod(yaw - offsetX, glm::radians(360.0f)); + camera->forward( { glm::cos(pitch) * glm::cos(yaw), glm::sin(pitch), glm::cos(pitch) * glm::sin(yaw) }); +} + +template +bool UserCamera::scroll(double delta, double min, double max) { + if (!isActive) return false; + + if constexpr (std::is_same_v) { + auto newFov = (float) glm::clamp(camera->getFieldOfView() + delta, min, max); + if (newFov != camera->getFieldOfView()) { + camera->fieldOfView(newFov); + return true; + } + } else if constexpr (std::is_same_v) { + const auto newWidth = (float) glm::clamp(camera->getWidth() + delta, min, max); + if (newWidth != camera->getWidth()) { + camera->setWidth(newWidth); + return true; + } + } else { + static_assert("Unhandled Camera Type"); + } + + return false; +} + +template +void UserCamera::press(Camera::Input key, float time) { + using Key = Camera::Input; + if (!isActive) return; + + if (!config.center.has_value()) { + const float distance = time * config.moveSpeed; + switch (key) { + case Key::Up: + camera->move(+camera->getForward() * distance); break; + case Key::Down: + camera->move(-camera->getForward() * distance); break; + case Key::Left: + camera->move(-camera->getRight() * distance); break; + case Key::Right: + camera->move(+camera->getRight() * distance); break; + } + } else { + reset(); + } +} + +template +void UserCamera::reset() { + refForward = camera->getForward(); + refLeft = -camera->getRight(); + pitch = yaw = 0; +} + +template class vlkx::UserCamera; +template class vlkx::UserCamera; \ No newline at end of file diff --git a/projs/shadow/shadow-engine/shadow-renderer/src/render/Geometry.cpp b/projs/shadow/shadow-engine/shadow-renderer/src/render/Geometry.cpp index 89db16e..7358c8b 100644 --- a/projs/shadow/shadow-engine/shadow-renderer/src/render/Geometry.cpp +++ b/projs/shadow/shadow-engine/shadow-renderer/src/render/Geometry.cpp @@ -1,11 +1,11 @@ #include using namespace Geo; -void Mesh::setTriData(std::vector& vertices, std::vector& indices) { - std::vector Vertices = { - { { 0.0f, -1.0f, 0.0f },{ 0.0f, 0.0f, 1.0 },{ 1.0f, 0.0f, 0.0 },{ 0.0, 1.0 } }, - { { 1.0f, 1.0f, 0.0f },{ 0.0f, 0.0f, 1.0 },{ 0.0f, 1.0f, 0.0 },{ 0.0, 0.0 } }, - { { -1.0f, 1.0f, 0.0f },{ 0.0f, 0.0f, 1.0 },{ 0.0f, 0.0f, 1.0 },{ 1.0, 0.0 } }, +void Mesh::setTriData(std::vector& vertices, std::vector& indices) { + std::vector Vertices = { + { { 0.0f, -1.0f, 0.0f },{ 0.0f, 0.0f, 1.0 },{ 0.0, 1.0 } }, + { { 1.0f, 1.0f, 0.0f },{ 0.0f, 0.0f, 1.0 },{ 0.0, 0.0 } }, + { { -1.0f, 1.0f, 0.0f },{ 0.0f, 0.0f, 1.0 },{ 1.0, 0.0 } }, }; std::vector Indices = { @@ -18,13 +18,13 @@ void Mesh::setTriData(std::vector& vertices, std::vector& indi indices = Indices; } -void Mesh::setQuadData(std::vector& vertices, std::vector& indices) { +void Mesh::setQuadData(std::vector& vertices, std::vector& indices) { - std::vector Vertices = { - { { -1.0f, -1.0f, 0.0f },{ 0.0f, 0.0f, 1.0 },{ 1.0f, 0.0f, 0.0 },{ 0.0, 1.0 } }, - { { -1.0f, 1.0f, 0.0f },{ 0.0f, 0.0f, 1.0 },{ 0.0f, 1.0f, 0.0 },{ 0.0, 0.0 } }, - { { 1.0f, 1.0f, 0.0f },{ 0.0f, 0.0f, 1.0 },{ 0.0f, 0.0f, 1.0 },{ 1.0, 0.0 } }, - { { 1.0f, -1.0f, 0.0f },{ 0.0f, 0.0f, 1.0 },{ 1.0f, 0.0f, 1.0 },{ 1.0, 1.0 } } + std::vector Vertices = { + { { -1.0f, -1.0f, 0.0f },{ 0.0f, 0.0f, 1.0 },{ 0.0, 1.0 } }, + { { -1.0f, 1.0f, 0.0f },{ 0.0f, 0.0f, 1.0 },{ 0.0, 0.0 } }, + { { 1.0f, 1.0f, 0.0f },{ 0.0f, 0.0f, 1.0 },{ 1.0, 0.0 } }, + { { 1.0f, -1.0f, 0.0f },{ 0.0f, 0.0f, 1.0 },{ 1.0, 1.0 } } }; std::vector Indices = { @@ -37,38 +37,38 @@ void Mesh::setQuadData(std::vector& vertices, std::vector& ind indices = Indices; } -void Mesh::setCubeData(std::vector& vertices, std::vector& indices) { - std::vector Vertices = { +void Mesh::setCubeData(std::vector& vertices, std::vector& indices) { + std::vector Vertices = { // Front - { { -1.0f, -1.0f, 1.0f },{ 0.0f, 0.0f, 1.0 },{ 1.0f, 0.0f, 0.0 },{ 0.0, 1.0 } }, // 0 - { { -1.0f, 1.0f, 1.0f },{ 0.0f, 0.0f, 1.0 },{ 0.0f, 1.0f, 0.0 },{ 0.0, 0.0 } }, // 1 - { { 1.0f, 1.0f, 1.0f },{ 0.0f, 0.0f, 1.0 },{ 0.0f, 0.0f, 1.0 },{ 1.0, 0.0 } }, // 2 - { { 1.0f, -1.0f, 1.0f },{ 0.0f, 0.0f, 1.0 },{ 1.0f, 0.0f, 1.0 },{ 1.0, 1.0 } }, // 3 + { { -1.0f, -1.0f, 1.0f },{ 0.0f, 0.0f, 1.0 },{ 0.0, 1.0 } }, // 0 + { { -1.0f, 1.0f, 1.0f },{ 0.0f, 0.0f, 1.0 },{ 0.0, 0.0 } }, // 1 + { { 1.0f, 1.0f, 1.0f },{ 0.0f, 0.0f, 1.0 },{ 1.0, 0.0 } }, // 2 + { { 1.0f, -1.0f, 1.0f },{ 0.0f, 0.0f, 1.0 },{ 1.0, 1.0 } }, // 3 // Back - { { 1.0, -1.0, -1.0 },{ 0.0f, 0.0f, -1.0 },{ 1.0f, 0.0f, 1.0 },{ 0.0, 1.0 } }, // 4 - { { 1.0f, 1.0, -1.0 },{ 0.0f, 0.0f, -1.0 },{ 0.0f, 1.0f, 1.0 },{ 0.0, 0.0 } }, // 5 - { { -1.0, 1.0, -1.0 },{ 0.0f, 0.0f, -1.0 },{ 0.0f, 1.0f, 1.0 },{ 1.0, 0.0 } }, // 6 - { { -1.0, -1.0, -1.0 },{ 0.0f, 0.0f, -1.0 },{ 1.0f, 0.0f, 1.0 },{ 1.0, 1.0 } }, // 7 + { { 1.0, -1.0, -1.0 },{ 0.0f, 0.0f, -1.0 },{ 0.0, 1.0 } }, // 4 + { { 1.0f, 1.0, -1.0 },{ 0.0f, 0.0f, -1.0 },{ 0.0, 0.0 } }, // 5 + { { -1.0, 1.0, -1.0 },{ 0.0f, 0.0f, -1.0 },{ 1.0, 0.0 } }, // 6 + { { -1.0, -1.0, -1.0 },{ 0.0f, 0.0f, -1.0 },{ 1.0, 1.0 } }, // 7 // Left - { { -1.0, -1.0, -1.0 },{ -1.0f, 0.0f, 0.0 },{ 0.0f, 0.0f, 1.0 },{ 0.0, 1.0 } }, // 8 - { { -1.0f, 1.0, -1.0 },{ -1.0f, 0.0f, 0.0 },{ 0.0f, 0.0f, 1.0 },{ 0.0, 0.0 } }, // 9 - { { -1.0, 1.0, 1.0 },{ -1.0f, 0.0f, 0.0 },{ 0.0f, 0.0f, 1.0 },{ 1.0, 0.0 } }, // 10 - { { -1.0, -1.0, 1.0 },{ -1.0f, 0.0f, 0.0 },{ 0.0f, 0.0f, 1.0 },{ 1.0, 1.0 } }, // 11 + { { -1.0, -1.0, -1.0 },{ -1.0f, 0.0f, 0.0 },{ 0.0, 1.0 } }, // 8 + { { -1.0f, 1.0, -1.0 },{ -1.0f, 0.0f, 0.0 },{ 0.0, 0.0 } }, // 9 + { { -1.0, 1.0, 1.0 },{ -1.0f, 0.0f, 0.0 },{ 1.0, 0.0 } }, // 10 + { { -1.0, -1.0, 1.0 },{ -1.0f, 0.0f, 0.0 },{ 1.0, 1.0 } }, // 11 // Right - { { 1.0, -1.0, 1.0 },{ 1.0f, 0.0f, 0.0 },{ 0.0f, 0.0f, 1.0 },{ 0.0, 1.0 } }, // 12 - { { 1.0f, 1.0, 1.0 },{ 1.0f, 0.0f, 0.0 },{ 0.0f, 0.0f, 1.0 },{ 0.0, 0.0 } }, // 13 - { { 1.0, 1.0, -1.0 },{ 1.0f, 0.0f, 0.0 },{ 0.0f, 0.0f, 1.0 },{ 1.0, 0.0 } }, // 14 - { { 1.0, -1.0, -1.0 },{ 1.0f, 0.0f, 0.0 },{ 0.0f, 0.0f, 1.0 },{ 1.0, 1.0 } }, // 15 + { { 1.0, -1.0, 1.0 },{ 1.0f, 0.0f, 0.0 },{ 0.0, 1.0 } }, // 12 + { { 1.0f, 1.0, 1.0 },{ 1.0f, 0.0f, 0.0 },{ 0.0, 0.0 } }, // 13 + { { 1.0, 1.0, -1.0 },{ 1.0f, 0.0f, 0.0 },{ 1.0, 0.0 } }, // 14 + { { 1.0, -1.0, -1.0 },{ 1.0f, 0.0f, 0.0 },{ 1.0, 1.0 } }, // 15 // Top - { { -1.0f, 1.0f, 1.0f },{ 0.0f, 1.0f, 0.0 },{ 0.0f, 0.0f, 1.0 },{ 0.0, 1.0 } }, // 16 - { { -1.0f, 1.0f, -1.0f },{ 0.0f, 1.0f, 0.0 },{ 0.0f, 0.0f, 1.0 },{ 0.0, 0.0 } }, // 17 - { { 1.0f, 1.0f, -1.0f },{ 0.0f, 1.0f, 0.0 },{ 0.0f, 0.0f, 1.0 },{ 1.0, 0.0 } }, // 18 - { { 1.0f, 1.0f, 1.0f },{ 0.0f, 1.0f, 0.0 },{ 0.0f, 0.0f, 1.0 },{ 1.0, 1.0 } }, // 19 + { { -1.0f, 1.0f, 1.0f },{ 0.0f, 1.0f, 0.0 },{ 0.0, 1.0 } }, // 16 + { { -1.0f, 1.0f, -1.0f },{ 0.0f, 1.0f, 0.0 },{ 0.0, 0.0 } }, // 17 + { { 1.0f, 1.0f, -1.0f },{ 0.0f, 1.0f, 0.0 },{ 1.0, 0.0 } }, // 18 + { { 1.0f, 1.0f, 1.0f },{ 0.0f, 1.0f, 0.0 },{ 1.0, 1.0 } }, // 19 // Bottom - { { -1.0f, -1.0, -1.0 },{ 0.0f, -1.0f, 0.0 },{ 0.0f, 0.0f, 1.0 },{ 0.0, 1.0 } }, // 20 - { { -1.0, -1.0, 1.0 },{ 0.0f, -1.0f, 0.0 },{ 0.0f, 0.0f, 1.0 },{ 0.0, 0.0 } }, // 21 - { { 1.0, -1.0, 1.0 },{ 0.0f, -1.0f, 0.0 },{ 0.0f, 0.0f, 1.0 },{ 1.0, 0.0 } }, // 22 - { { 1.0, -1.0, -1.0 },{ 0.0f, -1.0f, 0.0 },{ 0.0f, 0.0f, 1.0 },{ 1.0, 1.0 } }, // 23 + { { -1.0f, -1.0, -1.0 },{ 0.0f, -1.0f, 0.0 },{ 0.0, 1.0 } }, // 20 + { { -1.0, -1.0, 1.0 },{ 0.0f, -1.0f, 0.0 },{ 0.0, 0.0 } }, // 21 + { { 1.0, -1.0, 1.0 },{ 0.0f, -1.0f, 0.0 },{ 1.0, 0.0 } }, // 22 + { { 1.0, -1.0, -1.0 },{ 0.0f, -1.0f, 0.0 },{ 1.0, 1.0 } }, // 23 }; std::vector Indices = { @@ -97,8 +97,8 @@ void Mesh::setCubeData(std::vector& vertices, std::vector& ind } -void Mesh::setSphereData(std::vector& vertices, std::vector& indices) { - std::vector Vertices; +void Mesh::setSphereData(std::vector& vertices, std::vector& indices) { + std::vector Vertices; std::vector Indices; float latitudeBands = 20.0f; @@ -116,7 +116,7 @@ void Mesh::setSphereData(std::vector& vertices, std::vector& i float sinPhi = sin(phi); float cosPhi = cos(phi); - Vertex vs; + VertexAll vs; vs.texture.x = (longNumber / longitudeBands); // u vs.texture.y = (latNumber / latitudeBands); // v diff --git a/projs/shadow/shadow-engine/shadow-renderer/src/render/framebuffer/RenderPass.cpp b/projs/shadow/shadow-engine/shadow-renderer/src/render/framebuffer/RenderPass.cpp deleted file mode 100644 index dfb8cc9..0000000 --- a/projs/shadow/shadow-engine/shadow-renderer/src/render/framebuffer/RenderPass.cpp +++ /dev/null @@ -1,78 +0,0 @@ -#include -#include - -RenderPass::RenderPass() {} -RenderPass::~RenderPass() {} - - -void RenderPass::createVertexRenderPass(VkFormat format) { - // Set up color metadata - VkAttachmentDescription color = {}; - color.format = format; - color.samples = VK_SAMPLE_COUNT_1_BIT; - color.loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR; - color.storeOp = VK_ATTACHMENT_STORE_OP_STORE; - color.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE; - color.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE; - color.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; - color.finalLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR; - - // Set up subattachments - VkAttachmentReference colorReference = {}; - colorReference.attachment = 0; - colorReference.layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL; - - VkSubpassDescription subpass = {}; - subpass.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS; - subpass.colorAttachmentCount = 1; - subpass.pColorAttachments = &colorReference; - - // Prepare the Render Pass for creation - VkSubpassDependency dependency = {}; - dependency.srcSubpass = VK_SUBPASS_EXTERNAL; - dependency.dstSubpass = 0; - dependency.srcStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT; - dependency.dstStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT; - dependency.srcAccessMask = 0; - dependency.dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT; - - std::array attachments = { color }; - VkRenderPassCreateInfo createInfo = {}; - createInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO; - createInfo.attachmentCount = static_cast(attachments.size()); - createInfo.pAttachments = attachments.data(); - createInfo.subpassCount = 1; - createInfo.dependencyCount = 1; - createInfo.pDependencies = &dependency; - createInfo.pSubpasses = &subpass; - - // Create the Render Pass - if (vkCreateRenderPass(VulkanManager::getInstance()->getDevice()->logical, &createInfo, nullptr, &pass)) - throw std::runtime_error("Unable to create Render Pass 1"); -} - -void RenderPass::beginRenderPass(std::vector clearValues, VkCommandBuffer commands, VkFramebuffer framebuffer, VkExtent2D extent) { - // Prepare the Render Pass for start - VkRenderPassBeginInfo info = {}; - info.sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO; - info.renderPass = pass; - info.framebuffer = framebuffer; - - info.renderArea.offset = { 0, 0 }; - info.renderArea.extent = extent; - - info.pClearValues = clearValues.data(); - info.clearValueCount = static_cast(clearValues.size()); - - // Attempt to begin the pass - vkCmdBeginRenderPass(commands, &info, VK_SUBPASS_CONTENTS_INLINE); -} - -void RenderPass::endRenderPass(VkCommandBuffer commands) { - vkCmdEndRenderPass(commands); -} - - -void RenderPass::destroy() { - vkDestroyRenderPass(VulkanManager::getInstance()->getDevice()->logical, pass, nullptr); -} \ No newline at end of file diff --git a/projs/shadow/shadow-engine/shadow-renderer/src/render/geometry/SingleRenderer.cpp b/projs/shadow/shadow-engine/shadow-renderer/src/render/geometry/SingleRenderer.cpp deleted file mode 100644 index 659de8b..0000000 --- a/projs/shadow/shadow-engine/shadow-renderer/src/render/geometry/SingleRenderer.cpp +++ /dev/null @@ -1,85 +0,0 @@ -#include -#include - -#include - -void SingleRenderer::createSingleRenderer(Geo::MeshType type, glm::vec3 posIn, glm::vec3 scaleIn) { - - // Collect metadata about the swap chain - uint32_t imageCount = VulkanManager::getInstance()->getSwapchain()->images.size(); - VkExtent2D imageExtent = VulkanManager::getInstance()->getSwapchain()->extent; - - // Create the buffers - buffers.createBuffers(type); - - // Create the descriptor layout - descriptor.createAndAllocateSimple(imageCount); - descriptor.populate(imageCount, buffers.uniformBuffer.buffer, sizeof(Geo::UniformBufferObject)); - - // Create the pipeline - pipeline.create(imageExtent, descriptor.layout, VulkanManager::getInstance()->getRenderPass()->pass); - - // Set locals - position = posIn; - scale = scaleIn; - rotation = glm::mat4_cast(glm::quat(0, 0, 0, 0)); -} - -void SingleRenderer::updateUniforms(Camera camera) { - - // Prepare data to go into the buffers - Geo::UniformBufferObject ubo = {}; - - glm::mat4 scaleMatrix = glm::mat4(1.0f); - glm::mat4 rotationMatrix = getRotation(); - glm::mat4 translationMatrix = glm::mat4(1.0f); - - scaleMatrix = glm::scale(scaleMatrix, scale); - translationMatrix = glm::translate(translationMatrix, position); - - ubo.model = translationMatrix * rotationMatrix * scaleMatrix; - ubo.view = camera.getViewMatrix(); - ubo.proj = camera.getProjectionMatrix(); - - ubo.proj[1][1] *= -1; - - // Copy the buffers into the GPU - void* data; - vmaMapMemory(VulkanManager::getInstance()->getAllocator(), buffers.uniformBuffer.allocation, &data); - memcpy(data, &ubo, sizeof(ubo)); - vmaUnmapMemory(VulkanManager::getInstance()->getAllocator(), buffers.uniformBuffer.allocation); -} - -void SingleRenderer::draw() { - // Fetch the buffer we're going to insert commands to - VkCommandBuffer commands = VulkanManager::getInstance()->getCurrentCommandBuffer(); - - // Bind our pipeline - vkCmdBindPipeline(commands, VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline.pipeline); - - // Bind the vertex buffer - VkBuffer vertexBuffers[] = { - buffers.vertexBuffer.buffer - }; - - VkDeviceSize offsets[] = { - 0 - }; - - vkCmdBindVertexBuffers(commands, 0, 1, vertexBuffers, offsets); - - // Bind the index buffer - vkCmdBindIndexBuffer(commands, buffers.indexBuffer.buffer, 0, VK_INDEX_TYPE_UINT32); - - // Bind the uniform buffer - vkCmdBindDescriptorSets(commands, VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline.layout, 0, 1, &descriptor.set, 0, nullptr); - - // Draw the buffered geometry - vkCmdDrawIndexed(commands, static_cast(buffers.indices.size()), 1, 0, 0, 0); -} - -void SingleRenderer::destroy() { - pipeline.destroy(); - descriptor.destroy(); - buffers.destroy(); -} \ No newline at end of file diff --git a/projs/shadow/shadow-engine/shadow-renderer/src/render/pipeline/Pipeline.cpp b/projs/shadow/shadow-engine/shadow-renderer/src/render/pipeline/Pipeline.cpp new file mode 100644 index 0000000..42e0165 --- /dev/null +++ b/projs/shadow/shadow-engine/shadow-renderer/src/render/pipeline/Pipeline.cpp @@ -0,0 +1,312 @@ +#include "vlkx/render/shader/Pipeline.h" +#include "vlkx/vulkan/VulkanModule.h" +#include "shadow/util/File.h" +#include + +namespace vlkx { + + struct ShaderStage { + VkShaderStageFlagBits stage; + ShaderModule::CountedShader module; + }; + + VkPipelineViewportStateCreateInfo createViewport(const GraphicsPipelineBuilder::Viewport& port) { + return { + VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO, + nullptr, 0, 1, &port.viewport, 1, &port.scissor + }; + } + + VkPipelineColorBlendStateCreateInfo createBlend(const std::vector& states) { + return { + VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO, + nullptr, 0, VK_FALSE, VK_LOGIC_OP_CLEAR, static_cast(states.size()), states.data(), + { 0, 0, 0, 0 } + }; + } + + VkPipelineVertexInputStateCreateInfo createVertexInput(const std::vector& bindingDescs, + const std::vector& attrDescs) { + return { + VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO, + nullptr, 0, + static_cast(bindingDescs.size()), bindingDescs.data(), + static_cast(attrDescs.size()), attrDescs.data() + }; + } + + std::vector createShader(const std::map& shaderMap) { + std::vector stages; + stages.reserve(shaderMap.size()); + for (const auto& pair : shaderMap) { + stages.push_back({ pair.first, ShaderModule::CountedShader::get(pair.second, pair.second) }); + } + + return stages; + } + + std::vector createShaderStage(const std::vector& stages) { + static constexpr char entryPoint[] = "main"; + std::vector infos; + infos.reserve(stages.size()); + for (const auto& stage : stages) { + infos.push_back({ + VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO, + nullptr, 0, stage.stage, **stage.module, entryPoint, nullptr + }); + } + + return infos; + } + + ShaderModule::ShaderModule(const std::string &path) { + const shadowutil::FileData* file = shadowutil::loadFile(path); + const VkShaderModuleCreateInfo module { + VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO, + nullptr, 0, file->size, reinterpret_cast(file->data.data()) + }; + + if (vkCreateShaderModule(VulkanModule::getInstance()->getDevice()->logical, &module, nullptr, &shader) != VK_SUCCESS) + throw std::runtime_error("Unable to create shader module"); + } + + PipelineBuilder::PipelineBuilder(std::optional maxCache) { + const VkPipelineCacheCreateInfo info { + VK_STRUCTURE_TYPE_PIPELINE_CACHE_CREATE_INFO, + nullptr, 0, static_cast(maxCache.value_or(0)), nullptr + }; + + if (vkCreatePipelineCache(VulkanModule::getInstance()->getDevice()->logical, &info, nullptr, &cache) != VK_SUCCESS) + throw std::runtime_error("Unable to create pipeline cache"); + } + + void PipelineBuilder::setLayout(std::vector&& descs, + std::vector&& pushConstants) { + std::vector pushSizes (pushConstants.size()); + for (size_t i = 0; i < pushConstants.size(); i++) + pushSizes[i] = pushConstants[i].size; + + const auto totalSize = std::accumulate(pushSizes.begin(), pushSizes.end(), 0); + if (totalSize > 128) + throw std::runtime_error("Trying to set push constants of total size " + std::to_string(totalSize) + " into pipeline " + name); + + descLayouts = std::move(descs); + constants = std::move(pushConstants); + layoutInfo.emplace(VkPipelineLayoutCreateInfo { + VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO, + nullptr, 0, static_cast(descLayouts.size()), descLayouts.data(), + static_cast(constants.size()), constants.data() + }); + } + + GraphicsPipelineBuilder::GraphicsPipelineBuilder(std::optional maxCache) : PipelineBuilder(maxCache) { + assemblyInfo = { + VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO, + nullptr, 0, VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST, VK_FALSE + }; + + rasterizationInfo = { + VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO, + nullptr, 0, VK_FALSE, VK_FALSE, VK_POLYGON_MODE_FILL, VK_CULL_MODE_BACK_BIT, VK_FRONT_FACE_COUNTER_CLOCKWISE, + VK_FALSE, 0, 0, 0, 1 + }; + + multisampleInfo = { + VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO, + nullptr, 0, VK_SAMPLE_COUNT_1_BIT, VK_FALSE, 0, nullptr, VK_FALSE, VK_FALSE + }; + + depthStencilInfo = { + VK_STRUCTURE_TYPE_PIPELINE_DEPTH_STENCIL_STATE_CREATE_INFO, + nullptr, 0, VK_FALSE, VK_FALSE, + VK_COMPARE_OP_LESS_OR_EQUAL, VK_FALSE, VK_FALSE, + {}, {}, 0, 1 + }; + + dynamicStateInfo = { + VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO, + nullptr, 0, 0, nullptr + }; + } + + GraphicsPipelineBuilder& GraphicsPipelineBuilder::name(std::string &&name) { + setName(std::move(name)); + return *this; + } + + GraphicsPipelineBuilder& GraphicsPipelineBuilder::depthTest(bool enable, bool write) { + depthStencilInfo.depthTestEnable = enable; + depthStencilInfo.depthWriteEnable = write; + return *this; + } + + GraphicsPipelineBuilder& GraphicsPipelineBuilder::stencilTest(bool enable) { + depthStencilInfo.stencilTestEnable = enable; + return *this; + } + + GraphicsPipelineBuilder& GraphicsPipelineBuilder::multiSample(VkSampleCountFlagBits samples) { + multisampleInfo.rasterizationSamples = samples; + return *this; + } + + GraphicsPipelineBuilder& GraphicsPipelineBuilder::topology(VkPrimitiveTopology topology) { + assemblyInfo.topology = topology; + return *this; + } + + GraphicsPipelineBuilder& GraphicsPipelineBuilder::stencilOp(const VkStencilOpState &state, + VkStencilFaceFlags flags) { + if (flags & VK_STENCIL_FACE_FRONT_BIT) + depthStencilInfo.front = state; + if (flags & VK_STENCIL_FACE_BACK_BIT) + depthStencilInfo.back = state; + return *this; + } + + GraphicsPipelineBuilder& GraphicsPipelineBuilder::addVertex(uint32_t bindPoint, + VkVertexInputBindingDescription &&desc, + std::vector &&attrs) { + desc.binding = bindPoint; + for (auto& attr : attrs) + attr.binding = bindPoint; + bindingDescs.push_back(desc); + + attrDescs.reserve(attrDescs.size() + attrs.size()); + std::move(attrs.begin(), attrs.end(), std::back_inserter(attrDescs)); + attrs.clear(); + return *this; + } + + GraphicsPipelineBuilder& GraphicsPipelineBuilder::layout(std::vector &&descLayouts, + std::vector &&constants) { + setLayout(std::move(descLayouts), std::move(constants)); + return *this; + } + + GraphicsPipelineBuilder& GraphicsPipelineBuilder::viewport(const vlkx::GraphicsPipelineBuilder::Viewport &port, + bool flipY) { + viewportMeta.emplace(port); + + if (flipY) { + VkViewport& view = viewportMeta.value().viewport; + view.y += view.height; + view.height *= -1; + } + + rasterizationInfo.frontFace = flipY ? VK_FRONT_FACE_COUNTER_CLOCKWISE : VK_FRONT_FACE_CLOCKWISE; + return *this; + } + + GraphicsPipelineBuilder& GraphicsPipelineBuilder::renderPass(const VkRenderPass &pass, uint32_t subpass) { + passMeta.emplace(PassInfo { pass, subpass }); + return *this; + } + + GraphicsPipelineBuilder& GraphicsPipelineBuilder::colorBlend( + std::vector &&states) { + blendStates = std::move(states); + return *this; + } + + GraphicsPipelineBuilder& GraphicsPipelineBuilder::shader(VkShaderStageFlagBits stage, std::string &&file) { + shaders[stage] = std::move(file); + return *this; + } + + std::unique_ptr GraphicsPipelineBuilder::build() const { + if (!hasLayout()) + throw std::runtime_error("Pipeline " + getName() + " has no layout set"); + if (!viewportMeta.has_value()) + throw std::runtime_error("Pipeline " + getName() + " has no viewport set"); + if (!passMeta.has_value()) + throw std::runtime_error("Pipeline " + getName() + " has no render pass set"); + if (blendStates.empty()) + throw std::runtime_error("Pipeline " + getName() + " has no color blend states."); + if (shaders.empty()) + throw std::runtime_error("Pipeline " + getName() + " has no shaders bound"); + + const auto viewportState = createViewport(viewportMeta.value()); + const auto blendState = createBlend(blendStates); + const auto vertexState = createVertexInput(bindingDescs, attrDescs); + + const auto shaderStages = createShader(shaders); + const auto shaderStageInfo = createShaderStage(shaderStages); + VkPipelineLayout pipelineLayout; + if (vkCreatePipelineLayout(VulkanModule::getInstance()->getDevice()->logical, &getLayout(), nullptr, &pipelineLayout) != VK_SUCCESS) + throw std::runtime_error("Unable to create layout for pipeline " + getName()); + + const VkGraphicsPipelineCreateInfo createInfo { + VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO, nullptr, 0, + static_cast(shaderStageInfo.size()), shaderStageInfo.data(), + &vertexState, &assemblyInfo, nullptr, &viewportState, + &rasterizationInfo, &multisampleInfo, &depthStencilInfo, &blendState, &dynamicStateInfo, + pipelineLayout, passMeta->pass, passMeta->subpass, VK_NULL_HANDLE, 0 + }; + + VkPipeline pipeline; + if (vkCreateGraphicsPipelines(VulkanModule::getInstance()->getDevice()->logical, VK_NULL_HANDLE, 1, &createInfo, nullptr, &pipeline) != VK_SUCCESS) + throw std::runtime_error("Failed to create pipeline " + getName()); + + return std::unique_ptr { + new Pipeline(getName(), pipeline, pipelineLayout, VK_PIPELINE_BIND_POINT_GRAPHICS) + }; + } + + ComputePipelineBuilder& ComputePipelineBuilder::name(std::string &&name) { + setName(std::move(name)); + return *this; + } + + ComputePipelineBuilder& ComputePipelineBuilder::layout(std::vector &&descLayouts, + std::vector &&pushConstants) { + setLayout(std::move(descLayouts), std::move(pushConstants)); + return *this; + } + + ComputePipelineBuilder& ComputePipelineBuilder::shader(std::string &&path) { + shaderPath.emplace(std::move(path)); + return *this; + } + + std::unique_ptr ComputePipelineBuilder::build() const { + if (!hasLayout()) + throw std::runtime_error("Pipeline " + getName() + " has no layout set"); + if (!shaderPath.has_value()) + throw std::runtime_error("Pipeline " + getName() + " has no shader set"); + + const auto shaderStages = createShader({{VK_SHADER_STAGE_COMPUTE_BIT, shaderPath.value()}}); + const auto shaderStageInfo = createShaderStage(shaderStages); + if (shaderStageInfo.size() != 1) + throw std::runtime_error("Compute pipeline " + getName() + " must have exactly one shader bound"); + + VkPipelineLayout layout; + if (vkCreatePipelineLayout(VulkanModule::getInstance()->getDevice()->logical, &getLayout(), nullptr, + &layout) != VK_SUCCESS) + throw std::runtime_error("Unable to create layout for compute pipeline " + getName()); + + const VkComputePipelineCreateInfo createInfo{ + VK_STRUCTURE_TYPE_COMPUTE_PIPELINE_CREATE_INFO, + nullptr, 0, + shaderStageInfo[0], layout, VK_NULL_HANDLE, 0 + }; + + VkPipeline pipeline; + if (vkCreateComputePipelines(VulkanModule::getInstance()->getDevice()->logical, VK_NULL_HANDLE, 1, &createInfo, + nullptr, &pipeline) != VK_SUCCESS) + throw std::runtime_error("Unable to create compute pipeline " + getName()); + + return std::unique_ptr{ + new Pipeline{getName(), pipeline, layout, VK_PIPELINE_BIND_POINT_COMPUTE} + }; + } + + void Pipeline::bind(const VkCommandBuffer &buffer) const { + vkCmdBindPipeline(buffer, bindPoint, pipeline); + } + + Pipeline::~Pipeline() { + vkDestroyPipeline(VulkanModule::getInstance()->getDevice()->logical, pipeline, nullptr); + vkDestroyPipelineLayout(VulkanModule::getInstance()->getDevice()->logical, layout, nullptr); + } +} \ No newline at end of file diff --git a/projs/shadow/shadow-engine/shadow-renderer/src/render/render_pass/GPUPass.cpp b/projs/shadow/shadow-engine/shadow-renderer/src/render/render_pass/GPUPass.cpp new file mode 100644 index 0000000..c516451 --- /dev/null +++ b/projs/shadow/shadow-engine/shadow-renderer/src/render/render_pass/GPUPass.cpp @@ -0,0 +1,394 @@ +#include "vlkx/render/render_pass/GPUPass.h" + +namespace vlkx { + + inline bool needsSynchronization(const ImageUsage& prev, const ImageUsage& curr) { + if (curr == prev && curr.getAccess() == ImageUsage::Access::ReadOnly) + return false; + return true; + } + + void addUsageToSubpass(const ImageUsage& usage, RenderPassBuilder::SubpassDependency::SubpassMeta* pass) { + pass->stage |= usage.getStage(); + pass->access |= usage.getAccessFlags(); + } + + void CommonPass::addUsage(std::string &&name, UsageTracker &&tracker) { + for (const auto& pair : tracker.getUsageMap()) + validate(pair.first, name, false); + + tracker.add(getVirtualInitial(), tracker.getInitialUsage()); + if (tracker.getFinalUsage().has_value()) + tracker.add(getVirtualFinal(), tracker.getFinalUsage().value()); + + usageHistory.emplace(std::move(name), std::move(tracker)); + } + + VkImageLayout CommonPass::getInitialLayout(const std::string &name) const { + return getHistory(name).getUsageMap().begin()->second.getLayout(); + } + + VkImageLayout CommonPass::getFinalLayout(const std::string &name) const { + return getHistory(name).getUsageMap().rbegin()->second.getLayout(); + } + + VkImageLayout CommonPass::getSubpassLayout(const std::string &name, int subpass) const { + validate(subpass, name, false); + return getUsage(name, subpass)->getLayout(); + } + + void CommonPass::update(const std::string &name, MultiImageTracker &tracker) const { + tracker.update(name, getHistory(name).getUsageMap().rbegin()->second); + } + + const UsageTracker& CommonPass::getHistory(const std::string &name) const { + return usageHistory.at(name); + } + + const ImageUsage* CommonPass::getUsage(const std::string &name, int pass) const { + validate(pass, name, true); + const UsageTracker& history = getHistory(name); + const auto iter = history.getUsageMap().find(pass); + return iter != history.getUsageMap().end() ? &iter->second : nullptr; + } + + std::optional CommonPass::checkForSync(const std::string &name, int pass) const { + validate(pass, name, true); + const UsageTracker& history = getHistory(name); + const auto currIter = history.getUsageMap().find(pass); + if (currIter == history.getUsageMap().end()) + return std::nullopt; + const auto prevIter = std::prev(currIter); + + const ImageUsage& prevUsage = prevIter->second; + const ImageUsage& currUsage = currIter->second; + + if (!needsSynchronization(prevUsage, currUsage)) + return std::nullopt; + + const int prevSubpass = prevIter->first; + return CommonPass::Usages { prevSubpass, &prevUsage, &currUsage }; + } + + void CommonPass::validate(int pass, const std::string &image, bool includeVirtual) const { + if (includeVirtual) { + if (!(pass >= getVirtualInitial() && pass <= getVirtualFinal())) + throw std::runtime_error("Subpass out of range."); + } else { + if (!(pass >= 0 && pass < numPasses)) + throw std::runtime_error("nv Subpass out of range."); + } + } + + int GraphicsPass::add(const std::string &name, UsageTracker &&history, std::function &&getter, + const std::optional ops) { + verifyHistory(name, history); + + const std::optional needsGetter = getFirstRenderTarget(history); + if (needsGetter.has_value()) { + if (getter == nullptr) + throw std::runtime_error("Image " + name + " is used as a render target without a location getter."); + } else { + getter = nullptr; + } + + const int attachmentLocation = static_cast(metas.size()); + metas.insert( + { + name, + AttachmentMeta { + attachmentLocation, + std::move(getter), + getOps(name, history, ops), + {} + }, + } + ); + + addUsage(std::string(name), std::move(history)); + return attachmentLocation; + } + + GraphicsPass& GraphicsPass::addMultisample(const std::string &source, const std::string &dest, int pass) { + validate(pass, source, false); + + const auto source_iter = usageHistory.find(source); + if (source_iter == usageHistory.end()) + throw std::runtime_error("Usage history not found for source image " + source); + + const UsageTracker& source_history = source_iter->second; + if (!verifyImageUsage(source_history, pass, ImageUsage::Type::RenderTarget)) + throw std::runtime_error("Usage type for source image " + source + " must be render target."); + + const auto dest_iter = usageHistory.find(dest); + if (dest_iter == usageHistory.end()) + throw std::runtime_error("Usage history not found for destination image " + dest); + + const UsageTracker& dest_history = dest_iter->second; + if (!verifyImageUsage(dest_history, pass, ImageUsage::Type::Multisample)) + throw std::runtime_error("Usage type for destination image " + dest + " must be multisample"); + + auto& targetMap = metas[source].multisample; + const bool inserted = targetMap.insert( { pass, dest }).second; + + if (!inserted) + throw std::runtime_error("Image " + source + " is already bound to a multisample."); + + return *this; + } + + void GraphicsPass::setAttachments() { + for (const auto& pair : usageHistory) { + const std::string& name = pair.first; + const AttachmentMeta& meta = metas[name]; + builder->setAttachment(meta.index, { meta.ops, getInitialLayout(name), getFinalLayout(name) } ); + } + } + + void GraphicsPass::setSubpasses() { + for (int pass = 0; pass < numPasses; ++pass) { + std::vector colors; + std::vector multisamples; + VkAttachmentReference dsRef { VK_ATTACHMENT_UNUSED, VK_IMAGE_LAYOUT_UNDEFINED }; + + // Verify all images used, the long way around. + for (const auto& pair : usageHistory) { + const std::string& name = pair.first; + const ImageUsage* history = getUsage(name, pass); + if (history == nullptr) + continue; + + const AttachmentMeta& meta = metas[name]; + const VkAttachmentReference ref { static_cast(meta.index), history->getLayout() }; + + switch (history->getType()) { + // If we're looking at a render target, we need to figure out where it gets its' details from. + case ImageUsage::Type::RenderTarget: { + const int location = meta.getter(pass); + const auto iter = meta.multisample.find(pass); + if (iter != meta.multisample.end()) { + const std::string& target = iter->second; + const ImageUsage* targetUsage = getUsage(target, pass); + if (targetUsage == nullptr) + throw std::runtime_error("Expected target image to have a usage"); + multisamples.push_back( { location, metas[target].index, targetUsage->getLayout() }); + } + + colors.push_back( { location, static_cast(ref.attachment), ref.layout }); + + break; + } + + case ImageUsage::Type::DepthStencil: + if (dsRef.attachment != VK_ATTACHMENT_UNUSED) + throw std::runtime_error("Depth stencil used multiple times"); + dsRef = ref; + break; + + case ImageUsage::Type::Multisample: + break; + + default: + throw std::runtime_error("Unreachable?"); + } + } + + auto colorRefs = RenderPassBuilder::parseColorReferences(colors); + auto multisampleRefs = RenderPassBuilder::parseMutisampling(colorRefs.size(), multisamples); + builder->setSubpass(pass, std::move(colorRefs), std::move(multisampleRefs), dsRef); + } + } + + void GraphicsPass::setDependencies() { + if (getVirtualFinal() != numPasses) + throw std::runtime_error("Virtual subpass mismatch"); + for (int pass = 0; pass <= numPasses; ++pass) { + std::map deps; + + for (const auto& pair : usageHistory) { + const auto usage = checkForSync(pair.first, pass); + if (!usage.has_value()) continue; + + const ImageUsage& prev = usage.value().lastUsage; + const ImageUsage& curr = usage.value().currentUsage; + const int sourcePass = usage.value().lastSubpass; + + auto iter = deps.find(sourcePass); + if (iter == deps.end()) { + const RenderPassBuilder::SubpassDependency defaultDep { + { checkSubpass(sourcePass), VK_PIPELINE_STAGE_NONE_KHR, VK_ACCESS_NONE_KHR }, + { checkSubpass(pass), VK_PIPELINE_STAGE_NONE_KHR, VK_ACCESS_NONE_KHR }, + 0 + }; + + iter = deps.insert( { sourcePass, defaultDep } ).first; + } + + addUsageToSubpass(prev, &iter->second.source); + addUsageToSubpass(curr, &iter->second.destination); + } + + for (const auto& pair : deps) { + const RenderPassBuilder::SubpassDependency& dep = pair.second; + builder->addDependency(dep); + } + } + } + + std::unique_ptr GraphicsPass::build(int framebuffers) { + builder = std::make_unique(); + builder->setFramebufferCount(framebuffers); + + setAttachments(); + setSubpasses(); + setDependencies(); + return std::move(builder); + } + + std::optional GraphicsPass::getFirstRenderTarget(const UsageTracker &history) const { + for (const auto& pair : history.getUsageMap()) { + const int pass = pair.first; + if (isVirtual(pass)) continue; + + if (pair.second.getType() == ImageUsage::Type::RenderTarget) return pass; + } + + return std::nullopt; + } + + RenderPassBuilder::Attachment::OpsType GraphicsPass::getOps(const std::string &name, const UsageTracker &history, + const std::optional &userOps) const { + const ImageUsage::Type type = getUsageType(name, history); + + if (!userOps.has_value()) { + switch (type) { + case ImageUsage::Type::RenderTarget: return getDefaultOps(); + case ImageUsage::Type::DepthStencil: return getStencilOps(); + default: + throw std::runtime_error("Unreachable?"); + } + } + + const RenderPassBuilder::Attachment::OpsType& ops = userOps.value(); + switch (type) { + case ImageUsage::Type::RenderTarget: + case ImageUsage::Type::DepthStencil: + return ops; + default: + throw std::runtime_error("Unreachable?"); + } + } + + ImageUsage::Type GraphicsPass::getUsageType(const std::string &name, const UsageTracker &history) const { + ImageUsage::Type prev = ImageUsage::Type::DontCare; + + for (const auto& pair : history.getUsageMap()) { + if (isVirtual(pair.first)) continue; + + ImageUsage::Type type = pair.second.getType(); + if (type == ImageUsage::Type::Multisample) + type = ImageUsage::Type::RenderTarget; + + if (prev == ImageUsage::Type::DontCare) { + prev = type; + } else if (type != prev) { + throw std::runtime_error("Inconsistent usage type specified for " + name); + } + } + + if (prev == ImageUsage::Type::DontCare) + throw std::runtime_error("Image " + name + " has no usages."); + + return prev; + } + + bool GraphicsPass::verifyImageUsage(const UsageTracker &history, int subpass, ImageUsage::Type type) const { + const auto iter = history.getUsageMap().find(subpass); + return iter != history.getUsageMap().end() && iter->second.getType() == type; + } + + void GraphicsPass::verifyHistory(const std::string &image, const UsageTracker &history) const { + for (const auto& pair : history.getUsageMap()) { + const ImageUsage::Type type = pair.second.getType(); + if (type != ImageUsage::Type::RenderTarget && type != ImageUsage::Type::DepthStencil && type != ImageUsage::Type::Multisample) + throw std::runtime_error("Invalid usage of " + image + " at subpass " + std::to_string(pair.first)); + } + } + + ComputePass &ComputePass::add(std::string &&name, UsageTracker &&history) { + verify(name, history); + addUsage(std::move(name), std::move(history)); + return *this; + } + + void ComputePass::execute(const VkCommandBuffer &commands, uint32_t queueFamily, + const std::map &images, + const std::vector>& computeOps) const { + + if (computeOps.size() != numPasses) + throw std::runtime_error("Compute shader mismatches ops and passes."); + + if (getVirtualFinal() != numPasses) + throw std::runtime_error("Compute shader attempting to run too many subpasses"); + + for (int pass = 0; pass < numPasses; ++pass) { + for (const auto& pair : usageHistory) { + const std::string& image = pair.first; + const auto usage = checkForSync(image, pass); + if (!usage.has_value()) continue; + + const auto iter = images.find(image); + if (iter == images.end()) + throw std::runtime_error("Image " + image + " not provided"); + + barrier(commands, queueFamily, *iter->second, usage.value().lastUsage, usage.value().currentUsage); + } + + if (pass < numPasses) + computeOps[pass](); + } + } + + void ComputePass::barrier(const VkCommandBuffer &commands, uint32_t queueFamily, const VkImage &image, + const ImageUsage &prev, const ImageUsage ¤t) const { + const VkImageMemoryBarrier barrier { + VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER, + nullptr, + prev.getAccessFlags(), + current.getAccessFlags(), + prev.getLayout(), + current.getLayout(), + queueFamily, + queueFamily, + image, + { + VK_IMAGE_ASPECT_COLOR_BIT, + 0, + 1, + 0, + 1 + } + }; + + vkCmdPipelineBarrier( + commands, + prev.getStage(), + current.getStage(), + 0, + 0, + nullptr, + 0, + nullptr, + 1, + &barrier + ); + } + + void ComputePass::verify(const std::string &name, const UsageTracker &history) const { + for (const auto& pair : history.getUsageMap()) { + const ImageUsage::Type type = pair.second.getType(); + if (type != ImageUsage::Type::LinearAccess && type != ImageUsage::Type::Sampled && type != ImageUsage::Type::Transfer) + throw std::runtime_error("Compute shader using an attachment that is not guranteed to be readable."); + } + } +} \ No newline at end of file diff --git a/projs/shadow/shadow-engine/shadow-renderer/src/render/render_pass/GenericRenderPass.cpp b/projs/shadow/shadow-engine/shadow-renderer/src/render/render_pass/GenericRenderPass.cpp new file mode 100644 index 0000000..302a00d --- /dev/null +++ b/projs/shadow/shadow-engine/shadow-renderer/src/render/render_pass/GenericRenderPass.cpp @@ -0,0 +1,268 @@ +#include "vlkx/render/render_pass/GenericRenderPass.h" +#include +#include +#include "vlkx/vulkan/VulkanModule.h" +#include + +namespace vlkx { + + /** + * Creates the necessary Clear Value struct to erase the given attachment. + * @param attachment the attachment metadata + * @return the Clear Value that will erase the attachment's data + */ + VkClearValue createClearFor(const RenderPassBuilder::Attachment& attachment) { + + VkClearValue clear {}; + if (std::holds_alternative(attachment.ops)) + clear.color = { { 0, 0, 0, 0 } }; + else { + clear.depthStencil = { 1.0, 0 }; + } + + return clear; + } + + /** + * Convert a RenderPassBuilder attachment to a VkAttachmentDescription. + * Format will be UNDEFINED. + * Sample count will be 1. + */ + VkAttachmentDescription buildAttachment(const RenderPassBuilder::Attachment& attachment) { + VkAttachmentDescription descriptor { + {}, + VK_FORMAT_UNDEFINED, + VK_SAMPLE_COUNT_1_BIT, + VK_ATTACHMENT_LOAD_OP_DONT_CARE, + VK_ATTACHMENT_STORE_OP_DONT_CARE, + VK_ATTACHMENT_LOAD_OP_DONT_CARE, + VK_ATTACHMENT_STORE_OP_DONT_CARE, + attachment.layoutInitial, + attachment.layoutFinal + }; + + if (const auto& ops = std::get_if(&attachment.ops); ops != nullptr) { + descriptor.loadOp = ops->LOAD; + descriptor.storeOp = ops->STORE; + } else if (const auto& ops = std::get_if(&attachment.ops); ops != nullptr) { + descriptor.loadOp = ops->DEPTH_LOAD; + descriptor.storeOp = ops->DEPTH_STORE; + descriptor.stencilLoadOp = ops->STENCIL_LOAD; + descriptor.stencilStoreOp = ops->STENCIL_STORE; + } + + return descriptor; + } + + std::vector buildSubpassDescriptors(const std::vector& attachments) { + std::vector descriptors; + descriptors.reserve(attachments.size()); + + for (const auto& attachment : attachments) { + descriptors.emplace_back(VkSubpassDescription { + {}, + VK_PIPELINE_BIND_POINT_GRAPHICS, + 0, + nullptr, + static_cast(attachment.colorReferences.size()), + attachment.colorReferences.data(), + attachment.multisampleReferences.empty() ? nullptr : attachment.multisampleReferences.data(), + attachment.stencilDepthReference.has_value() ? &attachment.stencilDepthReference.value() : nullptr, + 0, + nullptr + } + ); + } + + return descriptors; + } + + VkSubpassDependency buildSubpassDependency(const RenderPassBuilder::SubpassDependency& dep) { + return VkSubpassDependency { + dep.source.index, + dep.destination.index, + dep.source.stage, + dep.destination.stage, + dep.source.access, + dep.destination.access, + dep.flags + }; + } + + std::vector countColorAttachments(const std::vector& attachments) { + std::vector count; + count.reserve(attachments.size()); + + for (const auto& attachment : attachments) + count.emplace_back(attachment.colorReferences.size()); + + return count; + } + + std::vector createFramebuffers(const VkRenderPass& renderPass, const std::vector> getters, int count, const VkExtent2D& extent) { + std::vector views(getters.size()); + + VkFramebufferCreateInfo framebufferCreate { + VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO, + nullptr, + {}, + renderPass, + static_cast(views.size()), + views.data(), + extent.width, + extent.height, + 1 + }; + + std::vector framebuffers(count); + for (int i = 0; i < framebuffers.size(); ++i) { + for (int image = 0; image < getters.size(); image++) + views[image] = getters[image](i).getView(); + + vkCreateFramebuffer(VulkanModule::getInstance()->getDevice()->logical, &framebufferCreate, nullptr, &framebuffers[i]); + } + + return framebuffers; + } + + std::vector RenderPassBuilder::parseColorReferences(std::vector meta) { + if (meta.empty()) + return {}; + + // Search for the highest location index'd attachment reference. + // Shaders can define negative locations, so we have to start as low as we can go, + // and work our way up until we find the highest. + // If we start at 0, we risk missing an all-negative location set. + int max = std::numeric_limits::min(); + for (const auto& attachment : meta) + max = std::max(max, attachment.location); + + std::vector references(max + 1, { VK_ATTACHMENT_UNUSED, VK_IMAGE_LAYOUT_UNDEFINED }); + for (const auto& attachment : meta) + references[attachment.location] = { static_cast(attachment.descriptionIdx), attachment.layout }; + + return references; + } + + std::vector RenderPassBuilder::parseMutisampling(int colorReferencesCount, std::vector meta) { + std::vector references(colorReferencesCount, { VK_ATTACHMENT_UNUSED, VK_IMAGE_LAYOUT_UNDEFINED }); + for (const auto& attachment : meta) + references[attachment.location] = { static_cast(attachment.descriptionIdx), attachment.layout }; + + return references; + } + + fluent RenderPassBuilder::setFramebufferCount(int count) { + framebufferCount = count; + return *this; + } + + fluent RenderPassBuilder::setAttachment(int idx, const Attachment &attachment) { + if (idx > clearValues.size()) + clearValues.resize(idx + 1); + clearValues.at(idx) = createClearFor(attachment); + + if (idx > attachmentDescriptors.size()) + attachmentDescriptors.resize(idx + 1); + attachmentDescriptors.at(idx) = buildAttachment(attachment); + + if (attachmentDescriptors.size() > attachmentGetters.size()) + attachmentGetters.resize(attachmentDescriptors.size()); + + return *this; + } + + fluent RenderPassBuilder::updateAttachmentBacking(int idx, + std::function &&getBacking) { + const Image& img = getBacking(idx); + attachmentDescriptors[idx].format = img.getFormat(); + attachmentDescriptors[idx].samples = img.getSamples(); + attachmentGetters.at(idx) = std::move(getBacking); + return *this; + } + + fluent RenderPassBuilder::setSubpass(int idx, std::vector &&color, + std::vector &&multisample, + VkAttachmentReference &depthStencil) { + if (multisample.empty()) + if (multisample.size() != color.size()) + throw std::runtime_error("Constructing a subpass with mismatched color and multisample attachments"); + + SubpassAttachments attachments { + std::move(color), std::move(multisample), depthStencil + }; + + subpassAttachments.emplace_back(attachments); + return *this; + } + + fluent RenderPassBuilder::addDependency(const SubpassDependency &dep) { + subpassDependencies.emplace_back(buildSubpassDependency(dep)); + return *this; + } + + std::unique_ptr RenderPassBuilder::build() const { + if (framebufferCount == 0) + throw std::runtime_error("No framebuffers in render pass"); + for (int i = 0; i < attachmentGetters.size(); i++) + if (attachmentGetters[i] == nullptr) + throw std::runtime_error("Image " + std::to_string(i) + " is not set in render pass"); + + const auto descriptors = buildSubpassDescriptors(subpassAttachments); + const VkRenderPassCreateInfo createInfo { + VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO, + nullptr, + {}, + static_cast(attachmentDescriptors.size()), + attachmentDescriptors.data(), + static_cast(descriptors.size()), + descriptors.data(), + static_cast(subpassDependencies.size()), + subpassDependencies.data() + }; + + VkRenderPass pass; + if (vkCreateRenderPass(VulkanModule::getInstance()->getDevice()->logical, &createInfo, nullptr, &pass) != VK_SUCCESS) + throw std::runtime_error("Unable to create render pass"); + + const auto framebufferSize = attachmentGetters[0](0).getExtent(); + + return std::make_unique ( + static_cast(descriptors.size()), pass, clearValues, framebufferSize, createFramebuffers(pass, attachmentGetters, framebufferCount.value(), framebufferSize), + countColorAttachments(subpassAttachments) + ); + } + + void RenderPass::execute(const VkCommandBuffer &commands, int imageIndex, + std::vector> ops) const { + const VkRenderPassBeginInfo begin { + VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO, + nullptr, + renderPass, + framebuffers[imageIndex], + { + {0, 0}, + extent + }, + static_cast(clearValues.size()), + clearValues.data() + }; + + vkCmdBeginRenderPass(commands, &begin, VK_SUBPASS_CONTENTS_INLINE); + + for (int i = 0; i < ops.size(); i++) { + if (i != 0) + vkCmdNextSubpass(commands, VK_SUBPASS_CONTENTS_INLINE); + ops[i](commands); + } + + vkCmdEndRenderPass(commands); + } + + RenderPass::~RenderPass() { + for (const auto& fb : framebuffers) + vkDestroyFramebuffer(VulkanModule::getInstance()->getDevice()->logical, fb, nullptr); + vkDestroyRenderPass(VulkanModule::getInstance()->getDevice()->logical, renderPass, nullptr); + } + +} \ No newline at end of file diff --git a/projs/shadow/shadow-engine/shadow-renderer/src/render/render_pass/ScreenRenderPass.cpp b/projs/shadow/shadow-engine/shadow-renderer/src/render/render_pass/ScreenRenderPass.cpp new file mode 100644 index 0000000..0b3775d --- /dev/null +++ b/projs/shadow/shadow-engine/shadow-renderer/src/render/render_pass/ScreenRenderPass.cpp @@ -0,0 +1,145 @@ +#include "vlkx/render/render_pass/ScreenRenderPass.h" +#include "vlkx/vulkan/abstraction/Image.h" +#include "vlkx/render/render_pass/GPUPass.h" +#include "vlkx/vulkan/VulkanModule.h" + +namespace vlkx { + + void checkSubpass(int pass, int high) { if (pass < 1 || pass > high) throw std::runtime_error("Subpass index too high"); } + + void addAttachment(const AttachmentConfig& config, GraphicsPass& pass, MultiImageTracker& tracker, GraphicsPass::LocationGetter&& getter, const std::function& populateHistory) { + const std::string& name = config.name; + if (!tracker.isTracking(name)) + throw std::runtime_error("Attempting to add image " + name + " that is not tracked"); + + UsageTracker history { tracker.get(name) }; + populateHistory(history); + + if (config.finalUsage.has_value()) + history.setFinal(config.finalUsage.value()); + + config.index = pass.add(name, std::move(history), std::move(getter), config.loadStoreOps); + pass.update(name, tracker); + } + + RendererConfig::RendererConfig(int passCount, std::vector>& destinations, bool toScreen, std::optional firstTransparent, std::optional firstOverlay) : renderImages(destinations) { + if (passCount < 1) + throw std::runtime_error("Creating a RendererConfig with less than 1 subpass."); + + if (firstTransparent.has_value()) + checkSubpass(firstTransparent.value(), passCount); + if (firstOverlay.has_value()) + checkSubpass(firstOverlay.value(), passCount); + + if (firstOverlay.has_value()) + numOverlayPasses = passCount - firstOverlay.value(); + if (firstTransparent.has_value()) { + numOpaquePasses = firstTransparent.value(); + numTransparentPasses = passCount - numOpaquePasses - numOverlayPasses; + } else { + numOpaquePasses = passCount - numOverlayPasses; + } + + rendersToScreen = toScreen; + } + + std::unique_ptr SimpleRenderPass::createBuilder(int framebuffers, + const vlkx::RendererConfig &config, + const vlkx::AttachmentConfig &color, + const vlkx::AttachmentConfig *multisample, + const vlkx::AttachmentConfig *depthStencil, + vlkx::MultiImageTracker &tracker) { + const int passes = config.passes(); + const int firstPass = 0; + const int lastPass = passes - 1; + + const auto getLocation = [](int pass) { return 0; }; + bool usesMultisample = multisample != nullptr; + bool usesDepth = depthStencil != nullptr; + + const bool passesUsingDepth = config.depthPasses() > 0; + + if (usesDepth) { + if (passesUsingDepth == 0) + throw std::runtime_error("Depth stencil defined, but never used."); + } else + if (passesUsingDepth > 0) + throw std::runtime_error("Depth stencil used, but never defined."); + + GraphicsPass graphicsPass(passes); + + addAttachment(color, graphicsPass, tracker, getLocation, [&](UsageTracker& history) { + if (usesMultisample) + history.add(lastPass, ImageUsage::multisample()); + else + history.add(firstPass, lastPass, ImageUsage::renderTarget(0)); + }); + + if (usesMultisample) { + addAttachment(*multisample, graphicsPass, tracker, getLocation, [&](UsageTracker& history) { + history.add(firstPass, lastPass, ImageUsage::renderTarget(0)); + }); + graphicsPass.addMultisample(multisample->name, color.name, lastPass); + } + + if (usesDepth) + addAttachment(*depthStencil, graphicsPass, tracker, getLocation, [&](UsageTracker& history) { + if (config.numOpaquePasses > 0) { + const int lastOpaque = config.numOpaquePasses - 1; + history.add(firstPass, lastOpaque, ImageUsage::depthStencil(ImageUsage::Access::ReadWrite)); + } + + if (config.numTransparentPasses > 0) { + const int firstTransparent = config.numOpaquePasses; + const int lastTransparent = firstTransparent + config.numTransparentPasses - 1; + history.add(firstTransparent, lastTransparent, ImageUsage::depthStencil(ImageUsage::Access::ReadOnly)); + } + }); + + return graphicsPass.build(framebuffers); + + } + + void ScreenRenderPassManager::initializeRenderPass() { + if (config.usesDepth()) { + depthStencilImage = MultisampleImage::createDepthStencil(VulkanModule::getInstance()->getSwapchain()->extent, std::nullopt); + } + + if (passBuilder == nullptr) + preparePassBuilder(); + + passBuilder->updateAttachmentBacking(destinationInfo.index.value(), [this](int index) -> const Image& { + return *config.renderImages[index]; + }); + + if (depthStencilImage != nullptr) + passBuilder->updateAttachmentBacking(depthStencilInfo.index.value(), [this](int index) -> const Image& { + return *depthStencilImage; + }); + + pass = passBuilder->build(); + } + + void ScreenRenderPassManager::preparePassBuilder() { + const bool usesDepth = depthStencilImage != nullptr; + const bool usesMultisampling = false; + + MultiImageTracker tracker; + destinationInfo.add(tracker, *config.renderImages[0]); + if (usesDepth) + depthStencilInfo.add(tracker, *depthStencilImage); + if (usesMultisampling) + // TODO + (void) 0; + + auto colorConfig = destinationInfo.makeConfig(); + if (config.rendersToScreen) + colorConfig.setUsage(ImageUsage::presentation()); + else + colorConfig.setUsage(ImageUsage::sampledFragment()); + const auto multisampleConfig = multisampleInfo.makeConfig(); + const auto depthConfig = depthStencilInfo.makeConfig(); + + passBuilder = SimpleRenderPass::createBuilder(config.renderImages.size(), config, colorConfig, usesMultisampling ? &multisampleConfig : nullptr, usesDepth ? &depthConfig : nullptr, tracker); + } +} diff --git a/projs/shadow/shadow-engine/shadow-renderer/src/render/shader/Descriptor.cpp b/projs/shadow/shadow-engine/shadow-renderer/src/render/shader/Descriptor.cpp deleted file mode 100644 index 9ec96f7..0000000 --- a/projs/shadow/shadow-engine/shadow-renderer/src/render/shader/Descriptor.cpp +++ /dev/null @@ -1,137 +0,0 @@ -#include -#include -#include - -#include - -Descriptor::Descriptor() {} -Descriptor::~Descriptor() {} - -void Descriptor::createAndAllocateSimple(uint32_t images) { - createSimpleLayout(); - createSimplePool(images); -} - -void Descriptor::createSimpleLayout() { - // Set up a binding - VkDescriptorSetLayoutBinding binding = {}; - binding.binding = 0; - binding.descriptorCount = 1; - binding.descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER; - binding.stageFlags = VK_SHADER_STAGE_VERTEX_BIT; - - // Prepare to create the layout - std::array bindings = { - binding - }; - - VkDescriptorSetLayoutCreateInfo info = {}; - info.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO; - info.bindingCount = static_cast(bindings.size()); - info.pBindings = bindings.data(); - - // Create the layout - if (vkCreateDescriptorSetLayout(VulkanManager::getInstance()->getDevice()->logical, &info, nullptr, &layout) != VK_SUCCESS) - throw std::runtime_error("Unable to create Descriptor layout"); -} - -void Descriptor::createSimplePool(uint32_t images) { - // Set the size of the pool we want - VkDescriptorPoolSize size = {}; - size.type = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER; - size.descriptorCount = images; - - std::array sizes = { - size - }; - - // Prepare to create the pool - VkDescriptorPoolCreateInfo createInfo = {}; - createInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO; - createInfo.poolSizeCount = static_cast(sizes.size()); - createInfo.pPoolSizes = sizes.data(); - createInfo.maxSets = images; - - // Create the pool - if (vkCreateDescriptorPool(VulkanManager::getInstance()->getDevice()->logical, &createInfo, nullptr, &pool) != VK_SUCCESS) - throw std::runtime_error("Unable to create Descriptor pool"); - - // Prepare to allocate the set - std::vector layouts(images, layout); - VkDescriptorSetAllocateInfo allocateInfo = {}; - allocateInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO; - allocateInfo.descriptorPool = pool; - allocateInfo.descriptorSetCount = images; - allocateInfo.pSetLayouts = layouts.data(); - - // Allocate the Set - if (vkAllocateDescriptorSets(VulkanManager::getInstance()->getDevice()->logical, &allocateInfo, &set) != VK_SUCCESS) - throw std::runtime_error("Unable to allocate Descriptor set"); -} - -void Descriptor::populate(uint32_t images, VkBuffer uniforms, size_t bufferSize) { - // Iterate images - for (size_t i = 0; i < images; i++) { - // Set up the uniform buffer - VkDescriptorBufferInfo buffer = {}; - buffer.buffer = uniforms; - buffer.offset = 0; - buffer.range = bufferSize; - - // Prepare to write the buffer into the Descriptor set - VkWriteDescriptorSet write = {}; - write.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET; - write.pNext = nullptr; - write.dstSet = set; - write.dstBinding = 0; - write.dstArrayElement = 0; - write.descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER; - write.descriptorCount = 1; - write.pBufferInfo = &buffer; - write.pImageInfo = nullptr; - write.pTexelBufferView = nullptr; - - std::array writes = { - write - }; - - // Write the buffer into the descriptor - vkUpdateDescriptorSets(VulkanManager::getInstance()->getDevice()->logical, static_cast(writes.size()), writes.data(), 0, nullptr); - } -} - -void Descriptor::destroy() { - vkDestroyDescriptorPool(VulkanManager::getInstance()->getDevice()->logical, pool, nullptr); - vkDestroyDescriptorSetLayout(VulkanManager::getInstance()->getDevice()->logical, layout, nullptr); -} - -VkDescriptorSetLayout createBindingWrapper(std::vector bindings) { - VkDescriptorSetLayoutCreateInfo info = {}; - info.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO; - info.bindingCount = static_cast(bindings.size()); - info.pBindings = bindings.data(); - - VkDescriptorSetLayout layout; - - // Create the layout - if (vkCreateDescriptorSetLayout(VulkanManager::getInstance()->getDevice()->logical, &info, nullptr, &layout) != VK_SUCCESS) - throw std::runtime_error("Unable to create Descriptor layout"); - - return layout; -} -VkDescriptorPool createPoolWrapper(std::vector sizes, uint32_t images) { - // Prepare to create the pool - VkDescriptorPoolCreateInfo createInfo = {}; - createInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO; - createInfo.poolSizeCount = static_cast(sizes.size()); - createInfo.pPoolSizes = sizes.data(); - createInfo.maxSets = images; - - VkDescriptorPool pool; - - // Create the pool - if (vkCreateDescriptorPool(VulkanManager::getInstance()->getDevice()->logical, &createInfo, nullptr, &pool) != VK_SUCCESS) - throw std::runtime_error("Unable to create Descriptor pool"); - - return pool; -} diff --git a/projs/shadow/shadow-engine/shadow-renderer/src/render/shader/GeoBuffers.cpp b/projs/shadow/shadow-engine/shadow-renderer/src/render/shader/GeoBuffers.cpp deleted file mode 100644 index 0664cc4..0000000 --- a/projs/shadow/shadow-engine/shadow-renderer/src/render/shader/GeoBuffers.cpp +++ /dev/null @@ -1,101 +0,0 @@ -#include -#include -#include - -GeoBuffers::GeoBuffers() {} -GeoBuffers::~GeoBuffers() {} - -void GeoBuffers::createBuffers(Geo::MeshType type) { - // Set vertex and indice data - switch (type) { - case Geo::MeshType::Triangle: - Geo::Mesh::setTriData(vertices, indices); - break; - case Geo::MeshType::Quad: - Geo::Mesh::setQuadData(vertices, indices); - break; - case Geo::MeshType::Cube: - Geo::Mesh::setCubeData(vertices, indices); - break; - case Geo::MeshType::Sphere: - Geo::Mesh::setSphereData(vertices, indices); - break; - } - - // Create buffers - createVertexBuffer(); - createIndexBuffer(); - createUniformBuffer(); -} - -void GeoBuffers::createVertexBuffer() { - // Gather the size of the buffer we need.. - VkDeviceSize bufferSize = sizeof(vertices[0]) * vertices.size(); - - // Create and bind a temporary buffer - VkTools::ManagedBuffer staging = VkTools::createGPUBuffer(bufferSize, VK_BUFFER_USAGE_TRANSFER_SRC_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, VulkanManager::getInstance()->getDevice()->logical, VulkanManager::getInstance()->getDevice()->physical); - - // Get ready to write into our GPU buffer - void* data; - vmaMapMemory(VulkanManager::getInstance()->getAllocator(), staging.allocation, &data); - - // Copy vertex data into the buffer - memcpy(data, vertices.data(), (size_t)bufferSize); - - // Unmap the buffer from stack space - vmaUnmapMemory(VulkanManager::getInstance()->getAllocator(), staging.allocation); - - // Prepare an area in the GPU that we cannot see, that the vertex data can live permanently. - vertexBuffer = VkTools::createGPUBuffer(bufferSize, VK_BUFFER_USAGE_TRANSFER_DST_BIT | VK_BUFFER_USAGE_VERTEX_BUFFER_BIT, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, VulkanManager::getInstance()->getDevice()->logical, VulkanManager::getInstance()->getDevice()->physical, false); - - // Copy our data from the staging buffer into the new permanent buffer - VkTools::copyGPUBuffer(staging.buffer, vertexBuffer.buffer, bufferSize, VulkanManager::getInstance()->getDevice()->logical, VulkanManager::getInstance()->getDevice()->graphicsQueue, VulkanManager::getInstance()->getDevice()->queueData.graphics); - - // Cleanup the staging area. - vmaDestroyBuffer(VulkanManager::getInstance()->getAllocator(), staging.buffer, staging.allocation); -} - -void GeoBuffers::createIndexBuffer() { - // Gather the size of the buffer we need.. - VkDeviceSize bufferSize = sizeof(indices[0]) * indices.size(); - - // Create and bind a temporary buffer - VkTools::ManagedBuffer staging = VkTools::createGPUBuffer(bufferSize, VK_BUFFER_USAGE_TRANSFER_SRC_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, VulkanManager::getInstance()->getDevice()->logical, VulkanManager::getInstance()->getDevice()->physical); - - // Get ready to write into our GPU buffer - void* data; - vmaMapMemory(VulkanManager::getInstance()->getAllocator(), staging.allocation, &data); - - // Copy index data into the buffer - memcpy(data, indices.data(), (size_t)bufferSize); - - // Unmap the buffer from stack space - vmaUnmapMemory(VulkanManager::getInstance()->getAllocator(), staging.allocation); - - // Prepare an area in the GPU that we cannot see, that the vertex data can live permanently. - indexBuffer = VkTools::createGPUBuffer(bufferSize, VK_BUFFER_USAGE_TRANSFER_DST_BIT | VK_BUFFER_USAGE_INDEX_BUFFER_BIT, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, VulkanManager::getInstance()->getDevice()->logical, VulkanManager::getInstance()->getDevice()->physical); - - // Copy our data from the staging buffer into the new permanent buffer - VkTools::copyGPUBuffer(staging.buffer, indexBuffer.buffer, bufferSize, VulkanManager::getInstance()->getDevice()->logical, VulkanManager::getInstance()->getDevice()->graphicsQueue, VulkanManager::getInstance()->getDevice()->queueData.graphics); - - // Cleanup the staging area. - vmaDestroyBuffer(VulkanManager::getInstance()->getAllocator(), staging.buffer, staging.allocation); -} - -void GeoBuffers::createUniformBuffer() { - // Allocate a buffer of the right size - we don't need to copy uniforms. - VkDeviceSize bufferSize = sizeof(Geo::UniformBufferObject); - - uniformBuffer = VkTools::createGPUBuffer(bufferSize, VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, VulkanManager::getInstance()->getDevice()->logical, VulkanManager::getInstance()->getDevice()->physical); -} - -void GeoBuffers::destroy() { - // Cleanup uniform buffers - vmaDestroyBuffer(VulkanManager::getInstance()->getAllocator(), uniformBuffer.buffer, uniformBuffer.allocation); - - // Cleanup index buffers - vmaDestroyBuffer(VulkanManager::getInstance()->getAllocator(), indexBuffer.buffer, indexBuffer.allocation); - - // Cleanup vertex buffers - vmaDestroyBuffer(VulkanManager::getInstance()->getAllocator(), vertexBuffer.buffer, vertexBuffer.allocation); -} \ No newline at end of file diff --git a/projs/shadow/shadow-engine/shadow-renderer/src/render/shader/Pipeline.cpp b/projs/shadow/shadow-engine/shadow-renderer/src/render/shader/Pipeline.cpp deleted file mode 100644 index ed37569..0000000 --- a/projs/shadow/shadow-engine/shadow-renderer/src/render/shader/Pipeline.cpp +++ /dev/null @@ -1,200 +0,0 @@ -#include -#include - -#include -#include -#include - -Pipeline::Pipeline() {} -Pipeline::~Pipeline() {} - -void Pipeline::create(VkExtent2D extent, VkDescriptorSetLayout set, VkRenderPass renderPass) { - createPipelineLayout(set); - createVertexPipeline(extent, renderPass); -} - -void Pipeline::createPipelineLayout(VkDescriptorSetLayout set) { - // Prepare to create the pipeline layout - VkPipelineLayoutCreateInfo info = {}; - info.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO; - info.setLayoutCount = 1; - info.pSetLayouts = &set; - - // Create the layout - if (vkCreatePipelineLayout(VulkanManager::getInstance()->getDevice()->logical, &info, nullptr, &layout) != VK_SUCCESS) - throw std::runtime_error("Unable to create Pipeline Layout."); -} - -std::vector Pipeline::readFile(const std::string& filename) { - // Prepare the stream for reading the file - std::ifstream file(filename, std::ios::ate | std::ios::binary); - - if (!file.is_open()) - throw std::runtime_error(std::string("Unable to open shader file: ").append(filename)); - - size_t fileSize = (size_t)file.tellg(); - - // Read the file into a vector - std::vector buffer(fileSize); - file.seekg(0); - file.read(buffer.data(), fileSize); - file.close(); - - return buffer; -} - -VkShaderModule Pipeline::createShaderModule(const std::vector& code) { - // Prepare to create the shader module - VkShaderModuleCreateInfo info = {}; - info.sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO; - info.codeSize = code.size(); - info.pCode = reinterpret_cast(code.data()); - - // Create the module - VkShaderModule shaderModule; - if (vkCreateShaderModule(VulkanManager::getInstance()->getDevice()->logical, &info, nullptr, &shaderModule) != VK_SUCCESS) - throw std::runtime_error("Unable to create Shader module from SPIR-V binary"); - - return shaderModule; -} - -void Pipeline::createVertexPipeline(VkExtent2D extent, VkRenderPass renderPass) { - - // ------- Stage: Shader ------- // - - // Read the vertex shader - auto vertexShaderCode = readFile("vlkx-resources/shader/SPIRV/basic.vert.spv"); - VkShaderModule vertexShaderModule = createShaderModule(vertexShaderCode); - - // Prepare to create the Shader Stage - VkPipelineShaderStageCreateInfo vertexShaderCreateInfo = {}; - vertexShaderCreateInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO; - vertexShaderCreateInfo.stage = VK_SHADER_STAGE_VERTEX_BIT; - vertexShaderCreateInfo.module = vertexShaderModule; - vertexShaderCreateInfo.pName = "main"; - - - // Read the fragment shader - auto fragmentShaderCode = readFile("vlkx-resources/shader/SPIRV/basic.frag.spv"); - VkShaderModule fragmentShaderModule = createShaderModule(fragmentShaderCode); - - // Prepare to create the Shader Stage - VkPipelineShaderStageCreateInfo fragmentShaderCreateInfo = {}; - fragmentShaderCreateInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO; - fragmentShaderCreateInfo.stage = VK_SHADER_STAGE_FRAGMENT_BIT; - fragmentShaderCreateInfo.module = fragmentShaderModule; - fragmentShaderCreateInfo.pName = "main"; - - VkPipelineShaderStageCreateInfo shaderStages[] = { - vertexShaderCreateInfo, - fragmentShaderCreateInfo - }; - - // ------- Stage: Shader Input ------- // - - auto bindingDescription = Geo::Vertex::getBindingDesc(); - auto attributeDescription = Geo::Vertex::getAttributeDesc(); - - VkPipelineVertexInputStateCreateInfo vertexInputCreateInfo = {}; - vertexInputCreateInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO; - vertexInputCreateInfo.vertexBindingDescriptionCount = 1; - vertexInputCreateInfo.pVertexBindingDescriptions = &bindingDescription; - vertexInputCreateInfo.vertexAttributeDescriptionCount = static_cast(attributeDescription.size()); - vertexInputCreateInfo.pVertexAttributeDescriptions = attributeDescription.data(); - - // ------- Stage: Input Assembly ------- // - - VkPipelineInputAssemblyStateCreateInfo inputAssemblyCreateInfo = {}; - inputAssemblyCreateInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO; - inputAssemblyCreateInfo.topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST; - inputAssemblyCreateInfo.primitiveRestartEnable = VK_FALSE; - - // ------- Stage: Rasterization ------- // - - VkPipelineRasterizationStateCreateInfo rasterCreateInfo = {}; - rasterCreateInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO; - rasterCreateInfo.depthClampEnable = VK_FALSE; - rasterCreateInfo.rasterizerDiscardEnable = VK_FALSE; - rasterCreateInfo.polygonMode = VK_POLYGON_MODE_FILL; - rasterCreateInfo.lineWidth = 1.0f; - rasterCreateInfo.cullMode = VK_CULL_MODE_BACK_BIT; - rasterCreateInfo.frontFace = VK_FRONT_FACE_CLOCKWISE; - rasterCreateInfo.depthBiasEnable = VK_FALSE; - rasterCreateInfo.depthBiasConstantFactor = 0.0f; - rasterCreateInfo.depthBiasClamp = 0.0f; - rasterCreateInfo.depthBiasSlopeFactor = 0.0f; - - // ------- Stage: MultiSample ------- // - - VkPipelineMultisampleStateCreateInfo multisampleCreateInfo = {}; - multisampleCreateInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO; - multisampleCreateInfo.sampleShadingEnable = VK_FALSE; - multisampleCreateInfo.rasterizationSamples = VK_SAMPLE_COUNT_1_BIT; - - // ------- Stage: Color Blending ------- // - - VkPipelineColorBlendAttachmentState colorBlendAttach = {}; - colorBlendAttach.colorWriteMask = VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_G_BIT | VK_COLOR_COMPONENT_B_BIT | VK_COLOR_COMPONENT_A_BIT; - colorBlendAttach.blendEnable = VK_FALSE; - - VkPipelineColorBlendStateCreateInfo colorBlendCreateInfo = {}; - colorBlendCreateInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO; - colorBlendCreateInfo.attachmentCount = 1; - colorBlendCreateInfo.pAttachments = &colorBlendAttach; - - // ------- Stage: Viewport ------- // - - VkViewport viewport = {}; - viewport.x = 0; - viewport.y = 0; - viewport.width = (float)extent.width; - viewport.height = (float)extent.height; - viewport.minDepth = 0.0f; - viewport.maxDepth = 1.0f; - - VkRect2D scissor = {}; - scissor.offset = { 0, 0 }; - scissor.extent = extent; - - VkPipelineViewportStateCreateInfo viewportCreateInfo = {}; - viewportCreateInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO; - viewportCreateInfo.viewportCount = 1; - viewportCreateInfo.pViewports = &viewport; - viewportCreateInfo.scissorCount = 1; - viewportCreateInfo.pScissors = &scissor; - - // ------- Create the Pipeline ------- // - - VkGraphicsPipelineCreateInfo pipelineInfo = {}; - pipelineInfo.sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO; - pipelineInfo.stageCount = 2; - pipelineInfo.pStages = shaderStages; - - pipelineInfo.pVertexInputState = &vertexInputCreateInfo; - pipelineInfo.pInputAssemblyState = &inputAssemblyCreateInfo; - pipelineInfo.pRasterizationState = &rasterCreateInfo; - pipelineInfo.pMultisampleState = &multisampleCreateInfo; - pipelineInfo.pDepthStencilState = nullptr; - pipelineInfo.pColorBlendState = &colorBlendCreateInfo; - pipelineInfo.pDynamicState = nullptr; - pipelineInfo.pViewportState = &viewportCreateInfo; - - pipelineInfo.layout = layout; - pipelineInfo.renderPass = renderPass; - pipelineInfo.subpass = 0; - - if (vkCreateGraphicsPipelines(VulkanManager::getInstance()->getDevice()->logical, VK_NULL_HANDLE, 1, &pipelineInfo, nullptr, &pipeline) != VK_SUCCESS) - throw std::runtime_error("Unable to create a Vertex rendering Pipeline."); - - - // ------- Cleanup temporary allocations ------- // - - vkDestroyShaderModule(VulkanManager::getInstance()->getDevice()->logical, vertexShaderModule, nullptr); - vkDestroyShaderModule(VulkanManager::getInstance()->getDevice()->logical, fragmentShaderModule, nullptr); -} - - -void Pipeline::destroy() { - vkDestroyPipeline(VulkanManager::getInstance()->getDevice()->logical, pipeline, nullptr); - vkDestroyPipelineLayout(VulkanManager::getInstance()->getDevice()->logical, layout, nullptr); -} \ No newline at end of file diff --git a/projs/shadow/shadow-engine/shadow-renderer/src/render/texture/RenderTexture.cpp b/projs/shadow/shadow-engine/shadow-renderer/src/render/texture/RenderTexture.cpp deleted file mode 100644 index e340453..0000000 --- a/projs/shadow/shadow-engine/shadow-renderer/src/render/texture/RenderTexture.cpp +++ /dev/null @@ -1,66 +0,0 @@ -#include -#include -#include - -SingleRenderTexture::SingleRenderTexture() {} -SingleRenderTexture::~SingleRenderTexture() {} - -void SingleRenderTexture::createViewsAndFramebuffer(std::vector images, VkFormat format, VkExtent2D extent, VkRenderPass pass) { - // Initialize members - this->swapChainImages = images; - this->swapChainImageExtent = extent; - - createViews(format); - createFramebuffer(extent, pass); -} - -void SingleRenderTexture::createViews(VkFormat format) { - // Prepare maximum size - swapChainImageViews.resize(swapChainImages.size()); - - // Iterate images and create views for each. - for (size_t i = 0; i < swapChainImages.size(); i++) { - swapChainImageViews[i] = VkTools::createImageView(swapChainImages[i], format, VK_IMAGE_ASPECT_COLOR_BIT, VulkanManager::getInstance()->getDevice()->logical); - } -} - -void SingleRenderTexture::createFramebuffer(VkExtent2D extent, VkRenderPass pass) { - // Prepare maximum size - swapChainFramebuffers.resize(swapChainImageViews.size()); - - // Iterate views and create a framebuffer for each. - for (size_t i = 0; i < swapChainImageViews.size(); i++) { - // Create an array with the image view as an attachment. - std::array attachments = { - swapChainImageViews[i] - }; - - // Prepare the creation of a new framebuffer. - // One attachment, width and height from the extent, and one layer (one viewport) - VkFramebufferCreateInfo info = {}; - info.sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO; - info.renderPass = pass; - info.attachmentCount = static_cast(attachments.size()); - info.pAttachments = attachments.data(); - info.width = extent.width; - info.height = extent.height; - info.layers = 1; - - // Create the framebuffer - if (vkCreateFramebuffer(VulkanManager::getInstance()->getDevice()->logical, &info, nullptr, &swapChainFramebuffers[i]) != VK_SUCCESS) - throw std::runtime_error("Unable to create framebuffer for a texture."); - } -} - -void SingleRenderTexture::destroy() { - // Destroy Image Views first - for (auto imageView : swapChainImageViews) { - vkDestroyImageView(VulkanManager::getInstance()->getDevice()->logical, imageView, nullptr); - } - - // Framebuffers - for (auto framebuffer : swapChainFramebuffers) { - vkDestroyFramebuffer(VulkanManager::getInstance()->getDevice()->logical, framebuffer, nullptr); - } - -} \ No newline at end of file diff --git a/projs/shadow/shadow-engine/shadow-renderer/src/temp/model/Builder.cpp b/projs/shadow/shadow-engine/shadow-renderer/src/temp/model/Builder.cpp new file mode 100644 index 0000000..5ae383b --- /dev/null +++ b/projs/shadow/shadow-engine/shadow-renderer/src/temp/model/Builder.cpp @@ -0,0 +1,238 @@ +#include "temp/model/Builder.h" + +namespace vlkxtemp { + using namespace vlkx; + using Geo::VertexAll; + using VertexData = PerVertexBuffer::NoShareMeta; + + std::unique_ptr createTex(const ModelBuilder::TextureSource& source) { + const auto usages = { ImageUsage::sampledFragment() }; + return std::make_unique(source, usages, ImageSampler::Config {}); + } + + void fillTextureMeta(const ModelBuilder::BindingPoints& binding, const ModelBuilder::TexturePerMesh& textures, const ModelBuilder::TexturePerMesh& sharedTextures, Descriptor::Meta* meta, Descriptor::ImageInfos* infos) { + *meta = {Image::getSampleType(), VK_SHADER_STAGE_FRAGMENT_BIT, {} }; + auto& texBindings = meta->bindings; + + for (size_t idx = 0; idx < (size_t) ModelBuilder::TextureType::Count; idx++) { + const auto type = (ModelBuilder::TextureType) idx; + const size_t numTextures = textures[idx].size() + sharedTextures[idx].size(); + if (numTextures != 0) { + const auto iter = binding.find(type); + if (iter == binding.end()) + throw std::runtime_error("Binding point of texture type " + std::to_string(idx) + " is not set"); + + texBindings.push_back({ iter->second, static_cast(numTextures) }); + + auto& metaMap = (*infos)[iter->second]; + metaMap.reserve(numTextures); + for (const auto& texture : textures[idx]) + metaMap.push_back(texture->getInfoForSampling()); + for (const auto& texture : sharedTextures[idx]) + metaMap.push_back(texture->getInfoForSampling()); + } + } + } + + std::vector createRanges(const ModelBuilder::ModelPushConstant& constants) { + std::vector ranges; + ranges.reserve(constants.constants.size()); + for (const auto& meta : constants.constants) + ranges.push_back( { constants.stage, meta.offset, meta.constants->getSize() }); + return ranges; + } + + VkVertexInputBindingDescription getBinding(uint32_t stride, bool instancing) { + return VkVertexInputBindingDescription{ 0, stride,instancing ? VK_VERTEX_INPUT_RATE_INSTANCE : VK_VERTEX_INPUT_RATE_VERTEX, }; + } + + void setVertexInput(const PerVertexBuffer& buffer, const std::vector& instanceBuffers, GraphicsPipelineBuilder* builder) { + uint32_t start = 0; + auto attributes = buffer.getAttrs(start); + start += attributes.size(); + + builder->addVertex(0, Geo::VertexAll::getBindingDesc(), std::move(attributes)); + + for (size_t i = 0; i < instanceBuffers.size(); i++) { + if (instanceBuffers[i] == nullptr) + throw std::runtime_error("PerInstanceVertexBuffer not specified"); + auto instanceAttrs = instanceBuffers[i]->getAttrs(start); + start += instanceAttrs.size(); + + auto instanceBinding = getBinding(instanceBuffers[i]->getSize(), true); + builder->addVertex(i + 1, std::move(instanceBinding), std::move(instanceAttrs)); + } + } + + void ModelBuilder::SingleMeshModel::load(ModelBuilder* builder) const { + const Wavefront obj(objFile, objIndexBase); + VertexData vertices { + {{ + PerVertexBuffer::VertexDataMeta { obj.indices }, + PerVertexBuffer::VertexDataMeta { obj.vertices } + }} + }; + + builder->vertexBuffer = std::make_unique(std::move(vertices), Geo::VertexAll::getAttributeDesc()); + + auto& meshTexs = builder->textures; + meshTexs.push_back({}); + for (const auto& pair : textureSources) { + const auto type = (size_t)pair.first; + const auto& sources = pair.second; + + meshTexs.back()[type].reserve(sources.size()); + for (const auto& source : sources) + meshTexs.back()[type].push_back({createTex(source)}); + } + } + + void ModelBuilder::MultiMeshModel::load(ModelBuilder* builder) const { + const ModelLoader loader(models, textures); + std::vector data; + data.reserve(loader.getMeshes().size()); + + for (const auto& mesh : loader.getMeshes()) + data.push_back({ PerVertexBuffer::VertexDataMeta { mesh.indices }, PerVertexBuffer::VertexDataMeta { mesh.vertices } }); + + builder->vertexBuffer = std::make_unique(VertexData { std::move(data)}, Geo::VertexAll::getAttributeDesc()); + + const auto usages = { ImageUsage::sampledFragment() }; + auto& meshTexs = builder->textures; + meshTexs.reserve(loader.getMeshes().size()); + for (const auto& mesh : loader.getMeshes()) { + meshTexs.push_back({}); + for (const auto& tex : mesh.textures) + meshTexs.back()[(size_t) tex.type].push_back(std::make_unique(tex.path, usages, ImageSampler::Config {})); + } + } + + ModelBuilder::ModelBuilder(std::string &&name, int frames, float aspect, const ModelResource &resource) + : frames(frames), aspectRatio(aspect), uniformBufferMeta(frames), pipelineBuilder(std::make_unique()) { + pipelineBuilder->name(std::move(name)); + resource.load(this); + } + + ModelBuilder& ModelBuilder::texture(TextureType type, const TextureSource &source) { + sharedTextures[(size_t) type].push_back(createTex(source)); + return *this; + } + + ModelBuilder& ModelBuilder::bindTextures(TextureType type, uint32_t point) { + bindPoints[type] = point; + return *this; + } + + ModelBuilder& ModelBuilder::instanceBuffer(vlkx::PerInstanceVertexBuffer* buffer) { + instanceBuffers.push_back(buffer); + return *this; + } + + ModelBuilder& ModelBuilder::uniform(VkShaderStageFlags stage, std::vector &&bindings) { + uniformMeta.push_back({ UniformBuffer::getDescriptorType(), stage, std::move(bindings) }); + return *this; + } + + ModelBuilder& ModelBuilder::uniformBuffer(uint32_t point, const vlkx::UniformBuffer &buffer) { + for (size_t frame = 0; frame < frames; frame++) { + const int chunk = buffer.isSingle() ? 0 : frame; + uniformBufferMeta[frame][point].push_back( buffer.getDescriptorInfo(chunk) ); + } + + return *this; + } + + ModelBuilder& ModelBuilder::pushStage(VkShaderStageFlags stage) { + if (!pushConstants.has_value()) + pushConstants.emplace(); + + pushConstants->stage = stage; + return *this; + } + + ModelBuilder& ModelBuilder::pushConstant(const vlkx::PushConstant* constant, uint32_t offset) { + if (!pushConstants.has_value()) + pushConstants.emplace(); + + pushConstants.value().constants.push_back( ModelPushConstant::Meta { constant, offset }); + return *this; + } + + ModelBuilder& ModelBuilder::shader(VkShaderStageFlagBits stage, std::string &&file) { + pipelineBuilder->shader(stage, std::move(file)); + return *this; + } + + std::vector ModelBuilder::createDescs() const { + std::vector descs(frames); + auto infos = uniformMeta; + infos.resize(infos.size() + 1); + + for (size_t frame = 0; frame < frames; frame++) { + descs[frame].reserve(textures.size()); + + for (const auto& tex : textures) { + Descriptor::ImageInfos image; + fillTextureMeta(bindPoints, tex, sharedTextures, &infos.back(), &image); + + descs[frame].push_back(std::make_unique(infos)); + descs[frame].back()->buffers(UniformBuffer::getDescriptorType(), uniformBufferMeta[frame]); + descs[frame].back()->images(Image::getSampleType(), image); + } + } + + return descs; + } + + std::unique_ptr ModelBuilder::build() { + if (pushConstants.has_value()) + if (pushConstants->constants.empty()) + throw std::runtime_error("Model sets push constant present but no data."); + + auto descs = createDescs(); + pipelineBuilder->layout( + { descs[0][0]->getLayout() }, + pushConstants.has_value() ? createRanges(pushConstants.value()) : std::vector {} + ); + + setVertexInput(*vertexBuffer, instanceBuffers, pipelineBuilder.get()); + + uniformMeta.clear(); + uniformBufferMeta.clear(); + + return std::unique_ptr { + new Model { + aspectRatio, std::move(vertexBuffer), std::move(instanceBuffers), std::move(pushConstants), + std::move(sharedTextures), std::move(textures), std::move(descs), std::move(pipelineBuilder) + } + }; + } + + void Model::update(bool opaque, const VkExtent2D &frame, VkSampleCountFlagBits samples, + const vlkx::RenderPass &pass, uint32_t subpass, bool flipY) { + + pipeline = (*pipelineBuilder) + .depthTest(true, opaque) + .multiSample(samples) + .viewport({ { 0, 0, static_cast(frame.width), static_cast(frame.height), 0, 1 }, { { 0, 0 }, frame } }) + .renderPass(*pass, subpass) + .colorBlend(std::vector(pass.getAttachsInSubpass(subpass), vlkx::Pipeline::getAlphaBlendState(!opaque))) + .build(); + } + + void Model::draw(const VkCommandBuffer &commands, int frame, uint32_t instances) const { + pipeline->bind(commands); + + for (size_t i = 0; i < perInstanceBuffers.size(); i++) + perInstanceBuffers[i]->bind(commands, i + 1, 0); + + if (pushConstants.has_value()) + for (const auto& meta : pushConstants->constants) + meta.constants->upload(commands, pipeline->getLayout(), frame, meta.offset, pushConstants->stage); + + for (size_t mesh = 0; mesh < textures.size(); mesh++) { + descriptors[frame][mesh]->bind(commands, pipeline->getLayout(), pipeline->getBind()); + vertexBuffer->draw(commands, 0, mesh, instances); + } + } +} \ No newline at end of file diff --git a/projs/shadow/shadow-engine/shadow-renderer/src/temp/model/Loader.cpp b/projs/shadow/shadow-engine/shadow-renderer/src/temp/model/Loader.cpp new file mode 100644 index 0000000..17cf42e --- /dev/null +++ b/projs/shadow/shadow-engine/shadow-renderer/src/temp/model/Loader.cpp @@ -0,0 +1,96 @@ +#include "temp/model/Loader.h" +#include +#include +#include +#include +#include + +namespace vlkxtemp { + + std::vector split (std::string_view s, char delim) { + std::vector result; + std::stringstream ss ((std::string(s))); + std::string item; + + while (std::getline (ss, item, delim)) { + result.push_back (item); + } + + return result; + } + + Wavefront::Wavefront(std::string_view path, size_t index_base) { + std::ifstream file(std::string(path), std::ios::binary); + + std::vector positions; + std::vector normals; + std::vector tex_coords; + std::map loaded_vertices; + + const auto parse_line = [&](std::string_view line) { + const size_t non_space = line.find_first_not_of(' '); + if (non_space == std::string::npos || line[0] == '#') + return; + + switch (line[non_space]) { + case 'v': { + switch (line[non_space + 1]) { + case ' ': { + const auto nums = split(line.substr(non_space + 2), ' '); + positions.emplace_back(stof(nums[0]), stof(nums[1]), stof(nums[2])); + break; + } + case 'n': { + // Normal. + const auto nums = split(line.substr(non_space + 3), ' '); + normals.emplace_back(glm::vec3{stof(nums[0]), stof(nums[1]), stof(nums[2])}); + break; + } + case 't': { + // Texture coordinates. + const auto nums = split(line.substr(non_space + 3), ' '); + tex_coords.emplace_back(glm::vec2{stof(nums[0]), stof(nums[1])}); + break; + } + default: + throw std::runtime_error("Unexpected symbol " + std::to_string(line[non_space + 1])); + } + break; + } + case 'f': { + for (const auto& seg : split(line.substr(non_space + 2), ' ')) { + const auto iter = loaded_vertices.find(seg); + if (iter != loaded_vertices.end()) { + indices.push_back(iter->second); + } else { + indices.push_back(vertices.size()); + loaded_vertices[seg] = vertices.size(); + const auto idxs = split(seg, '/'); + vertices.push_back(Geo::VertexAll { + positions.at(stoi(idxs[0]) - index_base), + normals.at(stoi(idxs[2]) - index_base), + tex_coords.at(stoi(idxs[1]) - index_base), + }); + } + } + break; + } + default: + throw std::runtime_error("Unexpected symbol in OBJ file: " + std::to_string(line[non_space])); + } + }; + + std::string line; + int line_num = 1; + try { + for (; std::getline(file, line); ++line_num) + parse_line(line); + } catch (const std::exception& e) { + throw std::runtime_error("Failed to parse obj file, error on line " + std::to_string(line_num) + ": " + line + "; " + e.what()); + } + } + + ModelLoader::ModelLoader(const std::string &model, const std::string &textures) { + + } +} \ No newline at end of file diff --git a/projs/shadow/shadow-engine/shadow-renderer/src/vulkan/CommandBuffer.cpp b/projs/shadow/shadow-engine/shadow-renderer/src/vulkan/CommandBuffer.cpp index 00cf528..e69de29 100644 --- a/projs/shadow/shadow-engine/shadow-renderer/src/vulkan/CommandBuffer.cpp +++ b/projs/shadow/shadow-engine/shadow-renderer/src/vulkan/CommandBuffer.cpp @@ -1,60 +0,0 @@ -#include -#include - -CommandBuffer::CommandBuffer() {} -CommandBuffer::~CommandBuffer() {} - -void CommandBuffer::createCommandPoolAndBuffers(size_t size) { - createCommandPool(); - allocateCommandBuffers(size); -} - -void CommandBuffer::createCommandPool() { - // Prepare queues - QueueFamilies families = VulkanManager::getInstance()->getDevice()->getQueues(); - - // Prepare command pool creation data - VkCommandPoolCreateInfo info = {}; - info.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO; - info.queueFamilyIndex = families.graphics; - info.flags = VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT; - - // Attempt creation - if (vkCreateCommandPool(VulkanManager::getInstance()->getDevice()->logical, &info, nullptr, &commands) != VK_SUCCESS) - throw std::runtime_error("Unable to create a command pool."); -} - -void CommandBuffer::allocateCommandBuffers(size_t size) { - // Prepare allocation info - buffers.resize(size); - - VkCommandBufferAllocateInfo info = {}; - info.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO; - info.commandPool = commands; - info.commandBufferCount = (uint32_t)buffers.size(); - - // Attempt allocation - if (vkAllocateCommandBuffers(VulkanManager::getInstance()->getDevice()->logical, &info, buffers.data()) != VK_SUCCESS) - throw std::runtime_error("Unable to allocate command buffer"); -} - -void CommandBuffer::beginCommandBuffer(VkCommandBuffer buffer) { - // Prepare to begin listening on this buffer - VkCommandBufferBeginInfo info = {}; - info.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO; - info.flags = VK_COMMAND_BUFFER_USAGE_SIMULTANEOUS_USE_BIT; // Allow writing to this buffer while another is being read. - - // Attempt to begin listening - if (vkBeginCommandBuffer(buffer, &info) != VK_SUCCESS) - throw std::runtime_error("Unable to begin a command buffer"); -} - -void CommandBuffer::endCommandBuffer(VkCommandBuffer buffer) { - // Attempt to end listening - if (vkEndCommandBuffer(buffer) != VK_SUCCESS) - throw std::runtime_error("Unable to end listening for a command buffer"); -} - -void CommandBuffer::destroy() { - vkDestroyCommandPool(VulkanManager::getInstance()->getDevice()->logical, commands, nullptr); -} diff --git a/projs/shadow/shadow-engine/shadow-renderer/src/vulkan/SwapChain.cpp b/projs/shadow/shadow-engine/shadow-renderer/src/vulkan/SwapChain.cpp index 600198e..d48d28b 100644 --- a/projs/shadow/shadow-engine/shadow-renderer/src/vulkan/SwapChain.cpp +++ b/projs/shadow/shadow-engine/shadow-renderer/src/vulkan/SwapChain.cpp @@ -1,5 +1,5 @@ #include -#include +#include #include "spdlog/spdlog.h" SwapChain::SwapChain() {} @@ -45,7 +45,7 @@ VkExtent2D SwapChain::chooseExtent(const VkSurfaceCapabilitiesKHR& capabilities) } void SwapChain::create(VkSurfaceKHR surface) { - SwapChainMeta info = VulkanManager::getInstance()->getDevice()->swapChain; + SwapChainMeta info = VulkanModule::getInstance()->getDevice()->swapChain; VkSurfaceFormatKHR chosenFormat = chooseFormat(info.formats); VkPresentModeKHR chosenMode = chooseMode(info.modes); @@ -66,7 +66,7 @@ void SwapChain::create(VkSurfaceKHR surface) { createInfo.imageArrayLayers = 1; // 2 for VR createInfo.imageUsage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT; - QueueFamilies queues = VulkanManager::getInstance()->getDevice()->getQueues(); + QueueFamilies queues = VulkanModule::getInstance()->getDevice()->getQueues(); uint32_t indices[] = { static_cast(queues.graphics), static_cast(queues.presentation) }; if (queues.graphics != queues.presentation) { @@ -86,19 +86,32 @@ void SwapChain::create(VkSurfaceKHR surface) { createInfo.oldSwapchain = VK_NULL_HANDLE; // Create the swap-chain - if (vkCreateSwapchainKHR(VulkanManager::getInstance()->getDevice()->logical, &createInfo, nullptr, &swapChain)) + if (vkCreateSwapchainKHR(VulkanModule::getInstance()->getDevice()->logical, &createInfo, nullptr, &swapChain)) throw std::runtime_error("Failed to create swap-chain"); - // Fetch our images from the swapchain - vkGetSwapchainImagesKHR(VulkanManager::getInstance()->getDevice()->logical, swapChain, &imageCount, nullptr); - images.resize(imageCount); - vkGetSwapchainImagesKHR(VulkanManager::getInstance()->getDevice()->logical, swapChain, &imageCount, images.data()); + // Set members + format = chosenFormat.format; + extent = chosenExtent; + + // Fetch our images from the swapchain + + uint32_t swapchainImgCount = 0; + vkGetSwapchainImagesKHR(VulkanModule::getInstance()->getDevice()->logical, swapChain, &swapchainImgCount, nullptr); + + std::vector swapchainImgs(swapchainImgCount); + vkGetSwapchainImagesKHR(VulkanModule::getInstance()->getDevice()->logical, swapChain, &swapchainImgCount, swapchainImgs.data()); + + images.resize(0); + images.reserve(imageCount); + for (const auto& img : swapchainImgs) { + images.emplace_back(std::make_unique(img, extent, format)); + } - // Set gloabls - format = chosenFormat.format; - extent = chosenExtent; } void SwapChain::destroy() { - vkDestroySwapchainKHR(VulkanManager::getInstance()->getDevice()->logical, swapChain, nullptr); + vkDestroySwapchainKHR(VulkanModule::getInstance()->getDevice()->logical, swapChain, nullptr); + /*for (auto & image : images) + image.reset(); + multisampleImg.reset(); */ } \ No newline at end of file diff --git a/projs/shadow/shadow-engine/shadow-renderer/src/vulkan/Tools.cpp b/projs/shadow/shadow-engine/shadow-renderer/src/vulkan/Tools.cpp new file mode 100644 index 0000000..50cc3c9 --- /dev/null +++ b/projs/shadow/shadow-engine/shadow-renderer/src/vulkan/Tools.cpp @@ -0,0 +1,123 @@ +#include +#include +#include "vlkx/vulkan/abstraction/Commands.h" + +API VmaAllocator VkTools::allocator; + +VkTools::ManagedImage VkTools::createImage(VkFormat format, VkImageUsageFlags flags, VkExtent3D extent) { + // Set up image metadata + VkImageCreateInfo info = {}; + info.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO; + info.imageType = VK_IMAGE_TYPE_3D; + info.pNext = nullptr; + info.format = format; + info.extent = extent; + info.mipLevels = 1; + info.arrayLayers = 1; + info.samples = VK_SAMPLE_COUNT_1_BIT; + info.tiling = VK_IMAGE_TILING_OPTIMAL; + info.usage = flags; + + // Prepare the managed image + ManagedImage image {}; + + // Set up image allocation + VmaAllocationCreateInfo allocateInfo = {}; + allocateInfo.usage = VMA_MEMORY_USAGE_GPU_ONLY; + + // Allocate + create the image + vmaCreateImage(allocator, &info, &allocateInfo, &image.image, &image.allocation, nullptr); + + return image; +} + +VkSampler VkTools::createSampler(VkFilter filters, VkSamplerAddressMode mode, uint32_t mipping, VkDevice dev) { + VkSamplerCreateInfo info = { + VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO, + nullptr, {}, filters, filters, + VK_SAMPLER_MIPMAP_MODE_LINEAR, mode, mode, mode, + 0, VK_TRUE, 16, VK_FALSE, + VK_COMPARE_OP_ALWAYS, 0, static_cast(mipping), + VK_BORDER_COLOR_INT_OPAQUE_BLACK, VK_FALSE + }; + + VkSampler sampler; + vkCreateSampler(dev, &info, nullptr, &sampler); + + return sampler; +} + +VkImageView VkTools::createImageView(VkImage image, VkFormat format, VkImageAspectFlags flags, uint32_t mipping, uint32_t layers, VkDevice device) { + // Raw information about the image + VkImageViewCreateInfo viewInfo = {}; + viewInfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO; + viewInfo.image = image; + viewInfo.viewType = layers == 1 ? VK_IMAGE_VIEW_TYPE_2D : VK_IMAGE_VIEW_TYPE_CUBE; + viewInfo.format = format; + + // Information about the things we want to create - size, mip levels. + viewInfo.subresourceRange.aspectMask = flags; + viewInfo.subresourceRange.baseMipLevel = 0; + viewInfo.subresourceRange.levelCount = mipping; + viewInfo.subresourceRange.baseArrayLayer = 0; + viewInfo.subresourceRange.layerCount = layers; + + VkImageView imageView; + if (vkCreateImageView(device, &viewInfo, nullptr, &imageView) != VK_SUCCESS) + throw std::runtime_error("Failed to create texture image view."); + + return imageView; +} + +VkTools::ManagedBuffer VkTools::createGPUBuffer(VkDeviceSize size, VkBufferUsageFlags usage, VkMemoryPropertyFlags properties, VkDevice logicalDevice, VkPhysicalDevice physicalDevice, bool hostVisible) { + // Prepare for creation of a buffer + VkBufferCreateInfo bufferInfo = {}; + bufferInfo.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO; + bufferInfo.size = size; + bufferInfo.usage = usage; + bufferInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE; + + ManagedBuffer buffer {}; + + VmaAllocationCreateInfo vmaInfo = {}; + vmaInfo.usage = hostVisible ? VMA_MEMORY_USAGE_CPU_ONLY : VMA_MEMORY_USAGE_GPU_ONLY; + vmaInfo.requiredFlags = properties; + + // Create the buffer. + if (VkResult status = vmaCreateBuffer(allocator, &bufferInfo, &vmaInfo, &buffer.buffer, &buffer.allocation, nullptr); status != VK_SUCCESS) + throw std::runtime_error("Unable to create GPU buffer: " + std::to_string(status)); + + return buffer; +} + +uint32_t findMemoryIndex(uint32_t type, VkMemoryPropertyFlags properties, VkPhysicalDevice physicalDevice) { + // Get the physical properties of the device. + VkPhysicalDeviceMemoryProperties physProperties; + vkGetPhysicalDeviceMemoryProperties(physicalDevice, &physProperties); + + // Iterate the device and search for a suitable index + for (uint32_t i = 0; i < physProperties.memoryTypeCount; i++) + // If the type matches, and the properties are what we desire, then ship it. + if ((type & (1 << i)) && ((physProperties.memoryTypes[i].propertyFlags & properties) == properties)) + return i; + + throw std::runtime_error("Unable to find a suitable memory type on the physical device."); +} + +void VkTools::immediateExecute(const std::function& execute, VulkanDevice* dev) { + vlkx::ImmediateCommand cmd({ dev->graphicsQueue, dev->queueData.graphics }); + cmd.run(execute); +} + +void VkTools::copyGPUBuffer(VkBuffer source, VkBuffer dest, VkDeviceSize length, VulkanDevice* dev) { + immediateExecute([&](const VkCommandBuffer& commands) { + // Prepare to copy the data between buffers + VkBufferCopy copyInfo = {}; + copyInfo.srcOffset = 0; + copyInfo.dstOffset = 0; + copyInfo.size = length; + + // Copy the data. + vkCmdCopyBuffer(commands, source, dest, 1, ©Info); + }, dev); +} \ No newline at end of file diff --git a/projs/shadow/shadow-engine/shadow-renderer/src/vulkan/ValidationAndExtension.cpp b/projs/shadow/shadow-engine/shadow-renderer/src/vulkan/ValidationAndExtension.cpp index 1ae8e66..4b8de03 100644 --- a/projs/shadow/shadow-engine/shadow-renderer/src/vulkan/ValidationAndExtension.cpp +++ b/projs/shadow/shadow-engine/shadow-renderer/src/vulkan/ValidationAndExtension.cpp @@ -41,6 +41,10 @@ std::vector ValidationAndExtension::getRequiredExtensions(SDL_Windo SDL_Vulkan_GetInstanceExtensions(window, &count, nullptr); std::vector extensions = { + #ifdef __APPLE__ + "VK_KHR_portability_enumeration", + #endif + "VK_KHR_get_physical_device_properties2" }; size_t additional_extension_count = extensions.size(); @@ -57,17 +61,16 @@ std::vector ValidationAndExtension::getRequiredExtensions(SDL_Windo } static VKAPI_ATTR VkBool32 VKAPI_CALL debugCallback( - VkDebugReportFlagsEXT flags, - VkDebugReportObjectTypeEXT objectType, - uint64_t object, - size_t location, - int32_t messageCode, - const char* pLayerPrefix, - const char* pMessage, - void* pUserData - ) { + VkDebugReportFlagsEXT flags, + VkDebugReportObjectTypeEXT objExt, + uint64_t obj, + size_t location, + int32_t code, + const char* layer, + const char* message, + void* user) { - std::cerr << "Validation from layer " << pLayerPrefix << ": " << pMessage << std::endl; + std::cerr << "Validation from layer " << layer << ": " << message << std::endl; return false; } diff --git a/projs/shadow/shadow-engine/shadow-renderer/src/vulkan/VulkanDevice.cpp b/projs/shadow/shadow-engine/shadow-renderer/src/vulkan/VulkanDevice.cpp index 5a71eb9..8162d65 100644 --- a/projs/shadow/shadow-engine/shadow-renderer/src/vulkan/VulkanDevice.cpp +++ b/projs/shadow/shadow-engine/shadow-renderer/src/vulkan/VulkanDevice.cpp @@ -26,6 +26,7 @@ void VulkanDevice::choosePhysicalDevice(VkInstance* vulkan, VkSurfaceKHR surface for (const auto& device : physicals) { VkPhysicalDeviceProperties props; vkGetPhysicalDeviceProperties(device, &props); + limits = props.limits; bool dedicated = props.deviceType == VK_PHYSICAL_DEVICE_TYPE_DISCRETE_GPU; spdlog::debug(std::string("Device: ") + props.deviceName + ", discrete: " + (dedicated ? "yes" : "no")); @@ -62,7 +63,7 @@ bool VulkanDevice::isSuitable(VkPhysicalDevice device, VkSurfaceKHR surface) { QueueFamilies VulkanDevice::checkQueues(VkPhysicalDevice device, VkSurfaceKHR surface) { QueueFamilies families; - // Enumerate queueues + // Enumerate queues uint32_t queueCount = 0; vkGetPhysicalDeviceQueueFamilyProperties(device, &queueCount, nullptr); diff --git a/projs/shadow/shadow-engine/shadow-renderer/src/vulkan/VulkanManager.cpp b/projs/shadow/shadow-engine/shadow-renderer/src/vulkan/VulkanManager.cpp deleted file mode 100644 index 95bc24a..0000000 --- a/projs/shadow/shadow-engine/shadow-renderer/src/vulkan/VulkanManager.cpp +++ /dev/null @@ -1,232 +0,0 @@ -#define VMA_IMPLEMENTATION - -#include - -#define VKTOOLS_IMPLEMENTATION - -#include - -#include -#include "spdlog/spdlog.h" -#include "spdlog/sinks/stdout_color_sinks.h" - -VulkanManager::VulkanManager() { rayTraceMode = false; } - -VulkanManager::~VulkanManager() = default; - -VulkanManager* VulkanManager::instance = nullptr; - - -VmaAllocator VkTools::g_allocator; -VkInstance VkTools::g_Instance = VK_NULL_HANDLE; -VkPhysicalDevice VkTools::g_PhysicalDevice = VK_NULL_HANDLE; -VkDevice VkTools::g_Device = VK_NULL_HANDLE; -uint32_t VkTools::g_QueueFamily = (uint32_t)-1; -VkQueue VkTools::g_Queue = VK_NULL_HANDLE; -VkDebugReportCallbackEXT VkTools::g_DebugReport = VK_NULL_HANDLE; - -VulkanManager* VulkanManager::getInstance() { - return VulkanManager::instance != nullptr ? VulkanManager::instance - : (VulkanManager::instance = new VulkanManager()); -} - -void VulkanManager::createAppAndVulkanInstance(bool enableValidation, ValidationAndExtension* validations) { - VkApplicationInfo info = {}; - info.sType = VK_STRUCTURE_TYPE_APPLICATION_INFO; - info.pApplicationName = "Sup"; - info.applicationVersion = VK_MAKE_API_VERSION(0, 1, 0, 0); - info.pEngineName = "Infinity Drive"; - info.engineVersion = VK_MAKE_API_VERSION(0, 1, 0, 0); - info.apiVersion = VK_API_VERSION_1_0; - - VkInstanceCreateInfo instanceInfo = {}; - instanceInfo.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO; - instanceInfo.pApplicationInfo = &info; - - auto extensions = validations->getRequiredExtensions(wnd, true); - instanceInfo.enabledExtensionCount = static_cast(extensions.size()); - instanceInfo.ppEnabledExtensionNames = &extensions[0]; - - auto layers = validations->requiredValidations; - if (enableValidation) { - instanceInfo.enabledLayerCount = static_cast(layers.size()); - instanceInfo.ppEnabledLayerNames = &layers[0]; - } else { - instanceInfo.enabledLayerCount = 0; - } - - auto status = vkCreateInstance(&instanceInfo, nullptr, &vulkan); - if (status != VK_SUCCESS) { - throw std::runtime_error("Failed to initialize Vulkan: " + std::to_string(status)); - } - -} - -void VulkanManager::initVulkan(SDL_Window* window) { - wnd = window; - validators = new ValidationAndExtension(); - - spdlog::info("Initializing Infinity Drive rendering engine"); - spdlog::default_logger()->set_level(spdlog::level::debug); - - if (!validators->checkValidationSupport()) - throw std::runtime_error("Validation not available"); - - createAppAndVulkanInstance(validationRequired, validators); - - validators->setupDebugCallback(validationRequired, vulkan); - - if (SDL_Vulkan_CreateSurface(window, vulkan, &surface) != SDL_TRUE) - throw std::runtime_error("Unable to create Vulkan Surface"); - - this->device = new VulkanDevice(); - this->device->choosePhysicalDevice(&vulkan, surface); - this->device->createLogicalDevice(surface, validationRequired, validators); - - VmaAllocatorCreateInfo allocatorInfo = {}; - allocatorInfo.physicalDevice = this->device->physical; - allocatorInfo.device = this->device->logical; - allocatorInfo.instance = this->vulkan; - vmaCreateAllocator(&allocatorInfo, &this->allocator); - VkTools::g_allocator = this->allocator; - VkTools::g_PhysicalDevice = this->device->physical; - VkTools::g_Device = this->device->logical; - VkTools::g_Instance = this->vulkan; - - this->swapchain = new SwapChain(); - this->swapchain->create(surface); - - this->renderPass = new RenderPass(); - - // Set up for vertex rendering - this->renderPass->createVertexRenderPass(swapchain->format); - this->renderTexture = new SingleRenderTexture(); - - this->renderTexture->createViewsAndFramebuffer(swapchain->images, swapchain->format, - swapchain->extent,renderPass->pass - ); - - this->buffers = new CommandBuffer(); - this->buffers->createCommandPoolAndBuffers(swapchain->images.size()); - - // Create semaphores for render events - VkSemaphoreCreateInfo semaphoreInfo = {}; - semaphoreInfo.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO; - vkCreateSemaphore(device->logical, &semaphoreInfo, nullptr, &newImageSem); - vkCreateSemaphore(device->logical, &semaphoreInfo, nullptr, &renderDoneSem); - - // Create fences for the frames - inFlight.resize(MAX_FRAMES); - VkFenceCreateInfo fenceInfo = {}; - fenceInfo.sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO; - fenceInfo.flags = VK_FENCE_CREATE_SIGNALED_BIT; - - for (size_t i = 0; i < MAX_FRAMES; i++) { - if (vkCreateFence(device->logical, &fenceInfo, nullptr, &inFlight[i]) != VK_SUCCESS) - throw std::runtime_error("Unable to create fence for a frame"); - } - - spdlog::info("Infinity Drive initialization finished."); -} - -void VulkanManager::startDraw() { - // Prepare for a new frame to start - vkAcquireNextImageKHR(device->logical, swapchain->swapChain, - std::numeric_limits::max(), newImageSem,VK_NULL_HANDLE, &imageIndex - ); - - vkWaitForFences(device->logical, 1, &inFlight[imageIndex], VK_TRUE, - std::numeric_limits::max() - ); - - vkResetFences(device->logical, 1, &inFlight[imageIndex]); - - // Fetch the next command buffer - currentCommandBuffer = buffers->buffers[imageIndex]; - buffers->beginCommandBuffer(currentCommandBuffer); - - // Setup render pass; setup clear color - VkClearValue clearColor = { 1.0f, 0.0f, 0.0f, 1.0f }; // Red - - // Execute render pass - renderPass->beginRenderPass({ clearColor }, currentCommandBuffer, dynamic_cast(renderTexture)->swapChainFramebuffers[imageIndex], dynamic_cast(renderTexture)->swapChainImageExtent); -} - -void VulkanManager::endDraw() { - // End command buffer first - renderPass->endRenderPass(currentCommandBuffer); - buffers->endCommandBuffer(currentCommandBuffer); - - // Prepare to submit all draw commands to the GPU - VkPipelineStageFlags waitStages[] = { - VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT - }; - - VkSubmitInfo submitInfo = {}; - submitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO; - submitInfo.commandBufferCount = 1; - submitInfo.pCommandBuffers = ¤tCommandBuffer; - submitInfo.pWaitDstStageMask = waitStages; - // Wait for the New Image semaphore, and signal the Render Done semaphore when finished - submitInfo.waitSemaphoreCount = 1; - submitInfo.pWaitSemaphores = &newImageSem; - submitInfo.signalSemaphoreCount = 1; - submitInfo.pSignalSemaphores = &renderDoneSem; - - // Submit. - vkQueueSubmit(VulkanManager::getInstance()->getDevice()->graphicsQueue, 1, &submitInfo, inFlight[imageIndex]); - - // Prepare to show the drawn frame on the screen. - VkPresentInfoKHR presentInfo = {}; - presentInfo.sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR; - presentInfo.swapchainCount = 1; - presentInfo.pSwapchains = &swapchain->swapChain; - presentInfo.pImageIndices = &imageIndex; - // Wait until render is finished before presenting. - presentInfo.waitSemaphoreCount = 1; - presentInfo.pWaitSemaphores = &renderDoneSem; - - // Show. - vkQueuePresentKHR(VulkanManager::getInstance()->getDevice()->presentationQueue, &presentInfo); - - // Wait for the GPU to catch up - vkQueueWaitIdle(VulkanManager::getInstance()->getDevice()->presentationQueue); -} - -void VulkanManager::cleanup() { - // Wait for the GPU to not be busy - vkDeviceWaitIdle(VulkanManager::getInstance()->getDevice()->logical); - - // Destroy our own data - vkDestroySemaphore(device->logical, renderDoneSem, nullptr); - vkDestroySemaphore(device->logical, newImageSem, nullptr); - - for (size_t i = 0; i < MAX_FRAMES; i++) { - vkDestroyFence(device->logical, inFlight[i], nullptr); - } - - buffers->destroy(); - renderTexture->destroy(); - renderPass->destroy(); - swapchain->destroy(); - - // Destroy the Vulkan Device - VulkanManager::getInstance()->getDevice()->destroy(); - - // Delete the layer validators. - validators->destroy(validationRequired, vulkan); - - // Delete the surface and Vulkan instance. - vkDestroySurfaceKHR(vulkan, surface, nullptr); - vkDestroyInstance(vulkan, nullptr); - - vmaDestroyAllocator(allocator); - - // Delete allocated memory for our own data. - delete buffers; - delete renderTexture; - delete renderPass; - delete swapchain; - delete device; - delete validators; -} \ No newline at end of file diff --git a/projs/shadow/shadow-engine/shadow-renderer/src/vulkan/VulkanModule.cpp b/projs/shadow/shadow-engine/shadow-renderer/src/vulkan/VulkanModule.cpp new file mode 100644 index 0000000..8f432ed --- /dev/null +++ b/projs/shadow/shadow-engine/shadow-renderer/src/vulkan/VulkanModule.cpp @@ -0,0 +1,348 @@ +#define VMA_IMPLEMENTATION + +#include +#include + +#include +#include "spdlog/spdlog.h" +#include "spdlog/sinks/stdout_color_sinks.h" +#include "core/ShadowApplication.h" +#include "core/SDL2Module.h" +#include "vlkx/render/render_pass/ScreenRenderPass.h" +#include + +#define CATCH(x) \ + try { x } catch (std::exception& e) { spdlog::error(e.what()); exit(0); } + +SHObject_Base_Impl(VulkanModule) + +std::unique_ptr renderPass; +bool renderingGeometry; +std::unique_ptr editorPass; + +std::unique_ptr editorRenderCommands; + +const std::unique_ptr& VulkanModule::getRenderPass() { + return renderPass; +} + +VulkanModule::VulkanModule() { instance = this; } + +VulkanModule::~VulkanModule() = default; + +VulkanModule* VulkanModule::instance = nullptr; + +VulkanModule* VulkanModule::getInstance() { + return VulkanModule::instance != nullptr ? VulkanModule::instance + : (VulkanModule::instance = new VulkanModule()); +} + +void VulkanModule::EnableEditor() { + editorEnabled = true; +} + +VkExtent2D VulkanModule::GetRenderExtent() { + if (editorEnabled) { + if (renderingGeometry) + return editorContentFrames[0]->getExtent(); + else + return swapchain->extent; + } else + return swapchain->extent; +} + +void VulkanModule::Recreate() { + vkDeviceWaitIdle(device->logical); + + device->swapChain = device->checkSwapchain(device->physical, surface); + + if (device->swapChain.capabilities.currentExtent.width == 0 && device->swapChain.capabilities.currentExtent.height == 0) { + []() { + SDL_Event event; + while (true) { + while (SDL_PollEvent(&event)) { + if (event.type == SDL_WINDOWEVENT && (event.window.event == SDL_WINDOWEVENT_MAXIMIZED || event.window.event == SDL_WINDOWEVENT_SHOWN || event.window.event == SDL_WINDOWEVENT_RESIZED || event.window.event == SDL_WINDOWEVENT_SIZE_CHANGED || event.window.event == SDL_WINDOWEVENT_RESTORED)) + return; + } + } + }(); + } + + device->swapChain = device->checkSwapchain(device->physical, surface); + + swapchain->destroy(); + swapchain->create(surface); + + renderPass->initializeRenderPass(); + editorPass->initializeRenderPass(); +} + +void VulkanModule::PreInit() { + spdlog::info("Vulkan Renderer Module loading.."); + + + auto shApp = ShadowEngine::ShadowApplication::Get(); + + ShadowEngine::ModuleManager &moduleManager = shApp.GetModuleManager(); + + auto sdl2module = moduleManager.GetModuleByType(); + + CATCH(initVulkan(sdl2module->_window->sdlWindowPtr);) + + IMGUI_CHECKVERSION(); + ImGui::CreateContext(); + ImGuiIO& io = ImGui::GetIO(); (void)io; + io.ConfigFlags |= ImGuiConfigFlags_DockingEnable; // Enable Docking + io.ConfigFlags |= ImGuiConfigFlags_ViewportsEnable; // Enable Multi-Viewport / Platform Windows + + // Setup Dear ImGui style + ImGui::StyleColorsDark(); + VkDescriptorPool imGuiPool; + VkDescriptorPoolSize pool_sizes[] = + { + { VK_DESCRIPTOR_TYPE_SAMPLER, 1000 }, + { VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 1000 }, + { VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, 1000 }, + { VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, 1000 }, + { VK_DESCRIPTOR_TYPE_UNIFORM_TEXEL_BUFFER, 1000 }, + { VK_DESCRIPTOR_TYPE_STORAGE_TEXEL_BUFFER, 1000 }, + { VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 1000 }, + { VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 1000 }, + { VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC, 1000 }, + { VK_DESCRIPTOR_TYPE_STORAGE_BUFFER_DYNAMIC, 1000 }, + { VK_DESCRIPTOR_TYPE_INPUT_ATTACHMENT, 1000 } + }; + + VkDescriptorPoolCreateInfo pool_info = {}; + pool_info.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO; + pool_info.flags = VK_DESCRIPTOR_POOL_CREATE_FREE_DESCRIPTOR_SET_BIT; + pool_info.maxSets = 1000 * IM_ARRAYSIZE(pool_sizes); + pool_info.poolSizeCount = (uint32_t)IM_ARRAYSIZE(pool_sizes); + pool_info.pPoolSizes = pool_sizes; + vkCreateDescriptorPool(getDevice()->logical, &pool_info, VK_NULL_HANDLE, &imGuiPool); + + // Setup Platform/Renderer backends + ImGui_ImplSDL2_InitForVulkan(wnd); + ImGui_ImplVulkan_InitInfo init_info = {}; + init_info.Instance = getVulkan(); + init_info.PhysicalDevice = getDevice()->physical; + init_info.Device = getDevice()->logical; + init_info.QueueFamily = getDevice()->queueData.graphics; + init_info.Queue = getDevice()->graphicsQueue; + init_info.PipelineCache = VK_NULL_HANDLE; + init_info.DescriptorPool = imGuiPool; + init_info.Subpass = 1; + init_info.MinImageCount = getSwapchain()->images.size(); + init_info.ImageCount = getSwapchain()->images.size(); + init_info.MSAASamples = VK_SAMPLE_COUNT_1_BIT; + init_info.Allocator = VK_NULL_HANDLE; + init_info.CheckVkResultFn = nullptr; + + + if (editorEnabled) { + editorContentFrames.resize(1); + for (size_t i = 0; i < 1; i++) { + vlkx::TextureImage::Meta meta { + {nullptr}, {vlkx::ImageUsage::renderTarget(0)}, VK_FORMAT_R8G8B8A8_SRGB, 640, 480, 4 + }; + editorContentFrames[i] = std::make_unique(0, vlkx::ImageSampler::Config {}, meta); + } + + editorRenderCommands = std::make_unique(editorContentFrames.size()); + } + + renderPass = std::make_unique(vlkx::RendererConfig { editorEnabled ? 1 : 2, editorEnabled ? editorContentFrames : swapchain->images, !editorEnabled } ); + renderPass->initializeRenderPass(); + editorPass = std::make_unique(vlkx::RendererConfig { 2, swapchain->images, true } ); + editorPass->initializeRenderPass(); + + ImGui_ImplVulkan_Init(&init_info, **(editorEnabled ? editorPass : renderPass)->getPass()); + + VkTools::immediateExecute([](const VkCommandBuffer& commands) { ImGui_ImplVulkan_CreateFontsTexture(commands); }, getDevice()); + + if (editorEnabled) { + editorRenderPlanes.resize(editorContentFrames.size()); + for (size_t i = 0; i < editorContentFrames.size(); i++) { + editorRenderPlanes[i] = ImGui_ImplVulkan_AddTexture(VkTools::createSampler(VK_FILTER_LINEAR, VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE, 0, device->logical), editorContentFrames[i]->getView(), VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL); + } + } + + renderingGeometry = false; +} + +void VulkanModule::Init() { +} + +void VulkanModule::BeginRenderPass(const std::unique_ptr& commands) { + const auto update = !editorEnabled ? [](const int frame) { ShadowEngine::ModuleManager::instance->Update(frame); } : nullptr; + + const auto res = commands->execute(commands->getFrame(), swapchain->swapChain, update, + [this](const VkCommandBuffer& buffer, int frame) { + (editorEnabled ? editorPass : renderPass)->getPass()->execute(buffer, frame, { + // Render our model + [&](const VkCommandBuffer &commands) { + if (!editorEnabled) { + renderingGeometry = true; + ShadowEngine::ModuleManager::instance->Render(const_cast(commands), frame); + ShadowEngine::ModuleManager::instance->LateRender(const_cast(commands), frame); + renderingGeometry = false; + } + }, + // Render ImGUI + [&](const VkCommandBuffer &commands) { + ImGui_ImplVulkan_NewFrame(); + ImGui_ImplSDL2_NewFrame(); + ImGui::NewFrame(); + + if (editorEnabled) + ImGui::Image(editorRenderPlanes[0], { 640, 480 }); + + ShadowEngine::ModuleManager::instance->OverlayRender(); + + ImGui::Render(); + ImGuiIO &io = ImGui::GetIO(); + (void) io; + + ImGui_ImplVulkan_RenderDrawData(ImGui::GetDrawData(), commands); + + // Update and Render additional Platform Windows + if (io.ConfigFlags & ImGuiConfigFlags_ViewportsEnable) { + ImGui::UpdatePlatformWindows(); + ImGui::RenderPlatformWindowsDefault(); + } + } + }); + } + ); + + if (res.has_value()) + ShadowEngine::ModuleManager::getInstance()->Recreate(); +} + +void VulkanModule::PreRender() { + if (editorEnabled) { + editorRenderCommands->executeSimple(editorRenderCommands->getFrame(), [](const int frame) { ShadowEngine::ModuleManager::instance->Update(frame); }, + [&](const VkCommandBuffer& buffer, int frame) { + renderPass->getPass()->execute(buffer, frame, { + [&](const VkCommandBuffer& commands) { + renderingGeometry = true; + ShadowEngine::ModuleManager::instance->Render(const_cast(commands), frame); + ShadowEngine::ModuleManager::instance->LateRender(const_cast(commands), frame); + renderingGeometry = false; + } + }); + } + ); + + } +} + + +void VulkanModule::OverlayRender() {} +void VulkanModule::AfterFrameEnd() {} +void VulkanModule::Render(VkCommandBuffer& commands, int frame) {} +void VulkanModule::Update(int frame) {} +void VulkanModule::Event(SDL_Event *e) { (void)e; } + +void VulkanModule::LateRender(VkCommandBuffer& commands, int frame) { +} + +void VulkanModule::Destroy() { + ImGui_ImplVulkan_Shutdown(); + ImGui_ImplSDL2_Shutdown(); + ImGui::DestroyContext(); +} + +void VulkanModule::createAppAndVulkanInstance(bool enableValidation, ValidationAndExtension* validations) { + VkApplicationInfo info = {}; + info.sType = VK_STRUCTURE_TYPE_APPLICATION_INFO; + info.pApplicationName = "Sup"; + info.applicationVersion = VK_MAKE_API_VERSION(0, 1, 0, 0); + info.pEngineName = "Infinity Drive"; + info.engineVersion = VK_MAKE_API_VERSION(0, 1, 0, 0); + info.apiVersion = VK_API_VERSION_1_0; + + VkInstanceCreateInfo instanceInfo = {}; + instanceInfo.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO; +#ifdef __APPLE__ + VkFlags instanceFlag = VK_INSTANCE_CREATE_ENUMERATE_PORTABILITY_BIT_KHR; +#else + VkFlags instanceFlag = 0; +#endif + instanceInfo.flags = instanceFlag; + instanceInfo.pApplicationInfo = &info; + + auto extensions = validations->getRequiredExtensions(wnd, true); + instanceInfo.enabledExtensionCount = static_cast(extensions.size()); + instanceInfo.ppEnabledExtensionNames = &extensions[0]; + + auto layers = validations->requiredValidations; + if (enableValidation) { + instanceInfo.enabledLayerCount = static_cast(layers.size()); + instanceInfo.ppEnabledLayerNames = &layers[0]; + } else { + instanceInfo.enabledLayerCount = 0; + } + + auto status = vkCreateInstance(&instanceInfo, nullptr, &vulkan); + if (status != VK_SUCCESS) { + throw std::runtime_error("Failed to initialize Vulkan: " + std::to_string(status)); + } + +} + +void VulkanModule::initVulkan(SDL_Window* window) { + wnd = window; + validators = new ValidationAndExtension(); + + spdlog::info("Initializing Infinity Drive rendering engine"); + spdlog::default_logger()->set_level(spdlog::level::debug); + + if (!validators->checkValidationSupport()) + throw std::runtime_error("Validation not available"); + + createAppAndVulkanInstance(validationRequired, validators); + + validators->setupDebugCallback(validationRequired, vulkan); + + if (SDL_Vulkan_CreateSurface(window, vulkan, &surface) != SDL_TRUE) + throw std::runtime_error("Unable to create Vulkan Surface"); + + this->device = new VulkanDevice(); + this->device->choosePhysicalDevice(&vulkan, surface); + this->device->createLogicalDevice(surface, validationRequired, validators); + + VmaAllocatorCreateInfo allocatorInfo = {}; + allocatorInfo.physicalDevice = this->device->physical; + allocatorInfo.device = this->device->logical; + allocatorInfo.instance = this->vulkan; + vmaCreateAllocator(&allocatorInfo, &this->allocator); + VkTools::allocator = allocator; + + this->swapchain = new SwapChain(); + this->swapchain->create(surface); + spdlog::info("Infinity Drive initialization finished."); +} + +void VulkanModule::cleanup() { + // Wait for the GPU to not be busy + vkDeviceWaitIdle(VulkanModule::getInstance()->getDevice()->logical); + + swapchain->destroy(); + + // Destroy the Vulkan Device + VulkanModule::getInstance()->getDevice()->destroy(); + + // Delete the layer validators. + validators->destroy(validationRequired, vulkan); + + // Delete the surface and Vulkan instance. + vkDestroySurfaceKHR(vulkan, surface, nullptr); + vkDestroyInstance(vulkan, nullptr); + + vmaDestroyAllocator(allocator); + + delete swapchain; + delete device; + delete validators; +} \ No newline at end of file diff --git a/projs/shadow/shadow-engine/shadow-renderer/src/vulkan/abstraction/Buffer.cpp b/projs/shadow/shadow-engine/shadow-renderer/src/vulkan/abstraction/Buffer.cpp new file mode 100644 index 0000000..821ec1b --- /dev/null +++ b/projs/shadow/shadow-engine/shadow-renderer/src/vulkan/abstraction/Buffer.cpp @@ -0,0 +1,239 @@ +#include "vlkx/vulkan/abstraction/Buffer.h" +#include "vlkx/vulkan/Tools.h" +#include "vlkx/vulkan/VulkanModule.h" +#include + +namespace vlkx { + + void executeBulkCopy(VkTools::ManagedBuffer buffer, const std::vector& meta) { + void* dst; + vmaMapMemory(VulkanModule::getInstance()->getAllocator(), buffer.allocation, &dst); + // GPU memory accessible through dst pointer + + for (const auto& info : meta) { + memcpy(static_cast(dst) + info.start, info.data, info.length); + } + + // Unmap GPU memory + vmaUnmapMemory(VulkanModule::getInstance()->getAllocator(), buffer.allocation); + } + + StagingBuffer::StagingBuffer(const vlkx::Buffer::BulkCopyMeta ©Meta) : dataSize(copyMeta.length) { + setBuffer(VkTools::createGPUBuffer(dataSize, VK_BUFFER_USAGE_TRANSFER_SRC_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, VulkanModule::getInstance()->getDevice()->logical, VulkanModule::getInstance()->getDevice()->physical)); + + executeBulkCopy(get(), copyMeta.metas); + } + + void StagingBuffer::copy(const VkBuffer &target) const { + VkTools::copyGPUBuffer(get().buffer, target, dataSize, VulkanModule::getInstance()->getDevice()); + } + + std::vector VertexBuffer::getAttrs(uint32_t start) const { + std::vector descs; + descs.reserve(attributes.size()); + + for (const auto& attr : attributes) { + descs.push_back(VkVertexInputAttributeDescription { + start++, 0, attr.format, attr.offset + }); + } + + return descs; + } + + void VertexBuffer::draw(const VkCommandBuffer &commands, uint32_t verts, uint32_t instances) { + vkCmdDraw(commands, verts, instances, 0, 0); + } + + void VertexBuffer::create(VkDeviceSize totalSize, bool dynamic, bool indexes) { + VkBufferUsageFlags usage = VK_BUFFER_USAGE_VERTEX_BUFFER_BIT; + VkMemoryPropertyFlags props; + + if (dynamic) { + props = VK_MEMORY_PROPERTY_HOST_COHERENT_BIT; + } else { + usage |= VK_BUFFER_USAGE_TRANSFER_DST_BIT; + props = VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT; + } + + if (indexes) + usage |= VK_BUFFER_USAGE_INDEX_BUFFER_BIT; + + setBuffer(VkTools::createGPUBuffer(totalSize, usage, props, VulkanModule::getInstance()->getDevice()->logical, VulkanModule::getInstance()->getDevice()->physical, dynamic)); + } + + DynamicBuffer::DynamicBuffer(size_t size, bool hasIndices, vlkx::VertexBuffer *buffer) : hasIndices(hasIndices), vertexBuffer(buffer) { + resize(size); + } + + void DynamicBuffer::resize(size_t pSize) { + if (pSize <= bufferSize()) return; + + if (pSize > 0) { + // todo: release the buffer & device memory + } + + size = pSize; + vertexBuffer->create(size, true, hasIndices); + } + + Buffer::BulkCopyMeta PerVertexBuffer::NoIndexBufferMeta::prepareCopy(vlkx::PerVertexBuffer *buffer) const { + auto& meshInfos = buffer->meshDataInfo.emplace().info; + meshInfos.reserve(perMeshVertices.size()); + + std::vector copyMetas; + copyMetas.reserve(perMeshVertices.size()); + + VkDeviceSize offset = 0; + for (const auto& verts : perMeshVertices) { + meshInfos.push_back(MeshDataNoIndex::Info { static_cast(verts.unitsPerMesh), offset }); + copyMetas.push_back(Buffer::CopyMeta { verts.data, verts.sizePerMesh, offset }); + offset += verts.sizePerMesh; + } + + return Buffer::BulkCopyMeta { offset, std::move(copyMetas) }; + } + + Buffer::BulkCopyMeta PerVertexBuffer::SharedIndexMeta::prepareCopy(vlkx::PerVertexBuffer *buffer) const { + auto& meshInfos = buffer->meshDataInfo.emplace().info; + meshInfos.reserve(meshes); + + VkDeviceSize offset = sharedIndices.sizePerMesh; + for (int i = 0; i < meshes; ++i) { + meshInfos.push_back(MeshDataIndex::Info { static_cast(sharedIndices.unitsPerMesh), 0, offset }); + offset += perMeshVertex.sizePerMesh; + } + + return Buffer::BulkCopyMeta { offset, { { sharedIndices.data, sharedIndices.sizePerMesh, 0 }, { perMeshVertex.data, perMeshVertex.sizePerMesh * meshes, sharedIndices.sizePerMesh } } }; + } + + + Buffer::BulkCopyMeta PerVertexBuffer::NoShareMeta::prepareCopy(vlkx::PerVertexBuffer *buffer) const { + auto& meshInfos = buffer->meshDataInfo.emplace().info; + meshInfos.reserve(perMeshMeta.size()); + + std::vector copyMetas; + copyMetas.reserve(perMeshMeta.size() * 2); + + VkDeviceSize offset = 0; + for (const auto& meta : perMeshMeta) { + const size_t indicesSize = meta.indices.sizePerMesh; + const size_t verticesSize = meta.vertices.sizePerMesh; + const VkDeviceSize verticesOffset = offset + indicesSize; + + meshInfos.push_back(MeshDataIndex::Info { static_cast(meta.indices.unitsPerMesh), offset, verticesOffset }); + copyMetas.push_back(Buffer::CopyMeta { meta.indices.data, indicesSize, offset }); + copyMetas.push_back(Buffer::CopyMeta { meta.vertices.data, verticesSize, verticesOffset }); + + offset += indicesSize + verticesSize; + } + + return Buffer::BulkCopyMeta { offset, std::move(copyMetas) }; + } + + void PerVertexBuffer::draw(const VkCommandBuffer &commands, uint32_t bind, int index, uint32_t instances) const { + if (const auto* meshNoIndex = std::get_if(&meshDataInfo); meshNoIndex != nullptr) { + const auto& meshInfo = meshNoIndex->info[index]; + vkCmdBindVertexBuffers(commands, bind, 1, &getBuffer(), &meshInfo.vertexStart); + vkCmdDraw(commands, meshInfo.vertexCount, instances, 0, 0); + } else if (const auto* meshIndex = std::get_if(&meshDataInfo); meshIndex != nullptr) { + const auto& meshInfo = meshIndex->info[index]; + vkCmdBindIndexBuffer(commands, getBuffer(), meshInfo.indexStart, VK_INDEX_TYPE_UINT32); + vkCmdBindVertexBuffers(commands, bind, 1, &getBuffer(), &meshInfo.vertexStart); + vkCmdDrawIndexed(commands, meshInfo.indexCount, instances, 0, 0, 0); + } + } + + StaticPerVertexBuffer::StaticPerVertexBuffer(const vlkx::PerVertexBuffer::BufferDataMeta &info, + std::vector &&attrs) : PerVertexBuffer(std::move(attrs)) { + const BulkCopyMeta copy = info.prepareCopy(this); + create(copy.length, false, info.hasIndices()); + const StagingBuffer staging(copy); + staging.copy(getBuffer()); + } + + void DynamicPerVertexBuffer::copyToDevice(const vlkx::PerVertexBuffer::BufferDataMeta &meta) { + const BulkCopyMeta copy = meta.prepareCopy(this); + resize(copy.length); + executeBulkCopy(get(), copy.metas); + } + + void PerInstanceVertexBuffer::bind(const VkCommandBuffer &commands, uint32_t bindPoint, int offset) const { + const VkDeviceSize size = sizePerInstance * offset; + vkCmdBindVertexBuffers(commands, bindPoint, 1, &getBuffer(), &size); + } + + StaticPerInstanceBuffer::StaticPerInstanceBuffer(uint32_t size, const void *data, uint32_t instances, + std::vector &&attrs) : PerInstanceVertexBuffer(size, std::move(attrs)) { + const uint32_t totalSize = size * instances; + create(totalSize, false, false); + + const BulkCopyMeta copy { totalSize, { {data, totalSize, 0} } }; + const StagingBuffer staging(copy); + staging.copy(getBuffer()); + } + + void DynamicPerInstanceBuffer::copyToDevice(const void *data, uint32_t instances) { + const uint32_t totalSize = getSize() * instances; + const BulkCopyMeta copy { totalSize, { { data, totalSize, 0 } } }; + resize(totalSize); + executeBulkCopy(get(), copy.metas); + } + + UniformBuffer::UniformBuffer(size_t chunkSize, int chunks) : DataBuffer(), chunkSize(chunkSize), numChunks(chunks) { + const VkDeviceSize alignment = VulkanModule::getInstance()->getDevice()->limits.minUniformBufferOffsetAlignment; + chunkLength = (chunkSize + alignment - 1) / alignment * alignment; + + data = new char[chunkSize * numChunks]; + setBuffer(VkTools::createGPUBuffer(chunkLength * numChunks, VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT, VulkanModule::getInstance()->getDevice()->logical, VulkanModule::getInstance()->getDevice()->physical, true)); + } + + void UniformBuffer::upload(int index) const { + checkIndex(index); + const VkDeviceSize srcOffset = chunkSize * index; + const VkDeviceSize dstOffset = chunkLength * index; + + // TODO: dstoffset? + executeBulkCopy(get(), { { data + srcOffset, chunkSize, 0 } } ); + } + + void UniformBuffer::upload(int index, VkDeviceSize dataSize, VkDeviceSize start) const { + checkIndex(index); + const VkDeviceSize srcOffset = chunkSize * index + start; + const VkDeviceSize dstOffset = chunkLength * index + start; + + // TODO: dstoffset? + executeBulkCopy(get(), { { data + srcOffset, dataSize, 0 } } ); + } + + VkDescriptorBufferInfo UniformBuffer::getDescriptorInfo(int index) const { + checkIndex(index); + return VkDescriptorBufferInfo { getBuffer(), chunkLength * index, chunkSize}; + } + + void UniformBuffer::checkIndex(int index) const { + if (index > numChunks) + throw std::runtime_error("Attempting to access uniform chunk " + std::to_string(index) + " out of range."); + + } + + PushConstant::PushConstant(size_t size, int numFrames) : sizePerFrame(static_cast(size)), numFrames(numFrames) { + if (size > 128) + throw std::runtime_error("Attempting to push constant of size " + std::to_string(size) + ", max ix 128."); + + data = new char[size * numFrames]; + } + + void PushConstant::upload(const VkCommandBuffer &commands, const VkPipelineLayout &pipelineLayout, int frame, + uint32_t offset, VkShaderStageFlags stage) const { + checkIndex(frame); + void* data = getData(frame); + vkCmdPushConstants(commands, pipelineLayout, stage, offset, sizePerFrame, data); + } + + void PushConstant::checkIndex(int index) const { + if (index > numFrames) + throw std::runtime_error("Attempting to access push constant for frame " + std::to_string(index) + " out of range."); + + } +} diff --git a/projs/shadow/shadow-engine/shadow-renderer/src/vulkan/abstraction/Commands.cpp b/projs/shadow/shadow-engine/shadow-renderer/src/vulkan/abstraction/Commands.cpp new file mode 100644 index 0000000..2c13eed --- /dev/null +++ b/projs/shadow/shadow-engine/shadow-renderer/src/vulkan/abstraction/Commands.cpp @@ -0,0 +1,177 @@ +#include "vlkx/vulkan/abstraction/Commands.h" +#include "vlkx/vulkan/VulkanModule.h" + +#include + +vlkx::CommandBuffer::CommandBuffer() { + dev = VulkanModule::getInstance()->getDevice(); +} + +VkCommandPool createPool(vlkx::Queue queue, bool shortLived) { + VkCommandPool pool; + VkCommandPoolCreateInfo poolCreateInfo = {}; + poolCreateInfo.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO; + poolCreateInfo.queueFamilyIndex = queue.queueIndex; + poolCreateInfo.flags = shortLived ? VK_COMMAND_POOL_CREATE_TRANSIENT_BIT : VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT; + + // Create the pool + if (vkCreateCommandPool(VulkanModule::getInstance()->getDevice()->logical, &poolCreateInfo, nullptr, &pool) != VK_SUCCESS) + throw std::runtime_error("Unable to allocate a temporary command pool"); + + return pool; +} + +std::vector allocateBuffers(const VkCommandPool& pool, uint32_t amount) { + const VkCommandBufferAllocateInfo allocate { + VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO, + nullptr, pool, VK_COMMAND_BUFFER_LEVEL_PRIMARY, amount + }; + + std::vector buffers(amount); + if (vkAllocateCommandBuffers(VulkanModule::getInstance()->getDevice()->logical, &allocate, buffers.data()) != VK_SUCCESS) + throw std::runtime_error("Unable to allocate command buffers"); + + return buffers; +} + +void recordCommands(const VkCommandBuffer& commands, VkCommandBufferUsageFlags flags, const vlkx::CommandBuffer::Command& record) { + const VkCommandBufferBeginInfo begin { + VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO, nullptr, flags, nullptr + }; + + if (vkBeginCommandBuffer(commands, &begin) != VK_SUCCESS) + throw std::runtime_error("Unable to begin listening on command buffer"); + if (record != nullptr) + record(commands); + if (vkEndCommandBuffer(commands) != VK_SUCCESS) + throw std::runtime_error("Unable to end listening on command buffer"); +} + +std::optional parseResult(VkResult result) { + switch(result) { + case VK_ERROR_OUT_OF_DATE_KHR: return result; + case VK_SUCCESS: + case VK_SUBOPTIMAL_KHR: + return std::nullopt; + + default: + throw std::runtime_error("Command buffer returned unknown result " + std::to_string(result)); + } +} + +vlkx::ImmediateCommand::ImmediateCommand(Queue queue) : queue(queue) { + const auto pool = createPool(queue, true); + setPool(pool); + commands = allocateBuffers(pool, 1)[0]; +} + +void vlkx::ImmediateCommand::run(const vlkx::CommandBuffer::Command &cmd) { + recordCommands(commands, VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT, cmd); + const VkSubmitInfo submit { + VK_STRUCTURE_TYPE_SUBMIT_INFO, + nullptr, 0, nullptr, nullptr, 1, + &commands, 0, nullptr + }; + + vkQueueSubmit(queue.queue, 1, &submit, VK_NULL_HANDLE); + vkQueueWaitIdle(queue.queue); +} + +vlkx::RenderCommand::RenderCommand(int frames) { + VulkanDevice* dev = VulkanModule::getInstance()->getDevice(); + + // Create semaphores for render events + VkSemaphoreCreateInfo semaphoreInfo = {}; + semaphoreInfo.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO; + vkCreateSemaphore(dev->logical, &semaphoreInfo, nullptr, &newImageSem); + vkCreateSemaphore(dev->logical, &semaphoreInfo, nullptr, &renderDoneSem); + + // Create fences for the frames + inFlight.resize(frames); + VkFenceCreateInfo fenceInfo = {}; + fenceInfo.sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO; + fenceInfo.flags = VK_FENCE_CREATE_SIGNALED_BIT; + + for (size_t i = 0; i < frames; i++) { + if (vkCreateFence(dev->logical, &fenceInfo, nullptr, &inFlight[i]) != VK_SUCCESS) + throw std::runtime_error("Unable to create fence for a frame"); + } + + const auto pool = createPool({ dev->graphicsQueue, dev->queueData.graphics }, false); + setPool(pool); + commands = allocateBuffers(pool, static_cast(frames)); +} + +std::optional vlkx::RenderCommand::execute(int frame, const VkSwapchainKHR &swapchain, + const vlkx::RenderCommand::Update &update, + const vlkx::RenderCommand::Command &cmd) { + + const VkDevice& logical = VulkanModule::getInstance()->getDevice()->logical; + + vkWaitForFences(logical, 1, &inFlight[imageIndex], VK_TRUE, + std::numeric_limits::max() + ); + + if (update != nullptr) + update(frame); + + uint32_t nextFrame; + // Prepare for a new frame to start + const auto result = parseResult(vkAcquireNextImageKHR(logical, swapchain, + std::numeric_limits::max(), newImageSem, VK_NULL_HANDLE, &nextFrame + )); + + if (result.has_value()) + return result; + + recordCommands(commands[imageIndex], VK_COMMAND_BUFFER_USAGE_SIMULTANEOUS_USE_BIT, [&cmd, nextFrame](const VkCommandBuffer& buffer) { + cmd(buffer, nextFrame); + }); + + constexpr VkPipelineStageFlags stage = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT; + const VkSubmitInfo submit { + VK_STRUCTURE_TYPE_SUBMIT_INFO, + nullptr, 1, &newImageSem, &stage, 1, &commands[imageIndex], 1, &renderDoneSem + }; + + vkResetFences(logical, 1, &inFlight[imageIndex]); + + if (VkResult res = vkQueueSubmit(VulkanModule::getInstance()->getDevice()->graphicsQueue, 1, &submit, inFlight[imageIndex]); res != VK_SUCCESS) + throw std::runtime_error("Failed to submit commands: " + std::to_string(res)); + + const VkPresentInfoKHR present { + VK_STRUCTURE_TYPE_PRESENT_INFO_KHR, + nullptr, 1, &renderDoneSem, 1, &swapchain, &nextFrame, nullptr + }; + + return parseResult(vkQueuePresentKHR(VulkanModule::getInstance()->getDevice()->presentationQueue, &present)); +} + + +std::optional vlkx::RenderCommand::executeSimple(int frame, + const vlkx::RenderCommand::Update &update, + const vlkx::RenderCommand::Command &cmd) { + if (update != nullptr) + update(frame); + + const VkDevice& logical = VulkanModule::getInstance()->getDevice()->logical; + + vkWaitForFences(logical, 1, &inFlight[imageIndex], VK_TRUE, + std::numeric_limits::max() + ); + + recordCommands(commands[imageIndex], VK_COMMAND_BUFFER_USAGE_SIMULTANEOUS_USE_BIT, [&cmd](const VkCommandBuffer& buffer) { + cmd(buffer, 0); + }); + + vkResetFences(logical, 1, &inFlight[imageIndex]); + + constexpr VkPipelineStageFlags stage = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT; + const VkSubmitInfo submit { + VK_STRUCTURE_TYPE_SUBMIT_INFO, + nullptr, 0, VK_NULL_HANDLE, &stage, 1, &commands[imageIndex], 0, VK_NULL_HANDLE + }; + + return std::make_optional(vkQueueSubmit(VulkanModule::getInstance()->getDevice()->graphicsQueue, 1, &submit, inFlight[imageIndex])); +} + diff --git a/projs/shadow/shadow-engine/shadow-renderer/src/vulkan/abstraction/Descriptor.cpp b/projs/shadow/shadow-engine/shadow-renderer/src/vulkan/abstraction/Descriptor.cpp new file mode 100644 index 0000000..452382a --- /dev/null +++ b/projs/shadow/shadow-engine/shadow-renderer/src/vulkan/abstraction/Descriptor.cpp @@ -0,0 +1,133 @@ +#include "vlkx/vulkan/abstraction/Descriptor.h" +#include +#include +#include + +using namespace vlkx; +// Returns 'pointer', assuming 'ExpectedType' and 'ActualType' are the same. +template +inline const ExpectedType* getPtr(const ActualType* pointer, std::true_type) { + return pointer; +} + +// Returns nullptr, assuming 'ExpectedType' and 'ActualType' are different. +template +inline const ExpectedType* getPtr(const ActualType* pointer, std::false_type) { + return nullptr; +} + +template +const ExpectedType* getPointer(const std::vector& container) { + return getPtr(container.data(), std::is_same()); +} + + +VkDescriptorPool createPool(std::vector metas) { + std::map sizes; + for (const auto& meta : metas) { + uint32_t length = 0; + for (const auto& binding : meta.bindings) + length += binding.length; + sizes[meta.type] += length; + } + + std::vector poolSizes; + for (const auto& pair : sizes) + poolSizes.push_back({ pair.first, pair.second }); + + const VkDescriptorPoolCreateInfo create { + VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO, + nullptr, 0, + 1, static_cast(poolSizes.size()), poolSizes.data() + }; + + VkDescriptorPool pool; + if (vkCreateDescriptorPool(VulkanModule::getInstance()->getDevice()->logical, &create, nullptr, &pool) != VK_SUCCESS) + throw std::runtime_error("Unable to create Descriptor Pool"); + + return pool; +} + +VkDescriptorSetLayout createLayout(std::vector metas, bool dynamic) { + size_t bindings = 0; + for (const auto& meta : metas) + bindings += meta.bindings.size(); + + std::vector layoutBindings; + layoutBindings.reserve(bindings); + for (const auto& meta : metas) + for (const auto& binding : meta.bindings) + layoutBindings.push_back({ binding.bindPoint, meta.type, binding.length, meta.stage, nullptr }); + + const VkDescriptorSetLayoutCreateInfo create { + VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO, + nullptr, static_cast(dynamic ? VK_DESCRIPTOR_SET_LAYOUT_CREATE_PUSH_DESCRIPTOR_BIT_KHR : 0), + static_cast(layoutBindings.size()), layoutBindings.data() + }; + + VkDescriptorSetLayout layout; + if (vkCreateDescriptorSetLayout(VulkanModule::getInstance()->getDevice()->logical, &create, nullptr, &layout) != VK_SUCCESS) + throw std::runtime_error("Unable to create Descriptor Set Layout"); + + return layout; +} + +VkDescriptorSet allocateSet(const VkDescriptorPool& pool, const VkDescriptorSetLayout& layout) { + const VkDescriptorSetAllocateInfo allocate { + VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO, + nullptr, pool, 1, &layout + }; + + VkDescriptorSet set; + if (vkAllocateDescriptorSets(VulkanModule::getInstance()->getDevice()->logical, &allocate, &set) != VK_SUCCESS) + throw std::runtime_error("Unable to allocate descriptor set"); + + return set; +} + +template +std::vector createWrites(const VkDescriptorSet& set, VkDescriptorType type, const std::map>& map) { + + std::vector sets; + sets.reserve(map.size()); + + for (const auto& pair : map) { + const auto& info = pair.second; + sets.push_back(VkWriteDescriptorSet { + VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET, + nullptr, set, pair.first, 0, + static_cast(info.size()), type, + getPointer(info), + getPointer(info), + getPointer(info) + }); + } + + return sets; +} + +StaticDescriptor::StaticDescriptor(std::vector metas) : Descriptor() { + pool = createPool(metas); + const auto layout = createLayout(metas, false); + setLayout(layout); + set = allocateSet(pool, layout); +} + +const StaticDescriptor& StaticDescriptor::buffers(VkDescriptorType type, const BufferInfos &infos) const { + return updateSet(createWrites(set, type, infos)); +} + +const StaticDescriptor& StaticDescriptor::images(VkDescriptorType type, const ImageInfos &infos) const { + return updateSet(createWrites(set, type, infos)); +} + +const StaticDescriptor& StaticDescriptor::updateSet(const std::vector &write) const { + vkUpdateDescriptorSets(VulkanModule::getInstance()->getDevice()->logical, write.size(), write.data(), 0, nullptr); + return *this; +} + +void StaticDescriptor::bind(const VkCommandBuffer &commands, const VkPipelineLayout &layout, + VkPipelineBindPoint bindPoint) const { + vkCmdBindDescriptorSets(commands, bindPoint, layout, 0, 1, &set, 0, nullptr); +} + diff --git a/projs/shadow/shadow-engine/shadow-renderer/src/vulkan/abstraction/Image.cpp b/projs/shadow/shadow-engine/shadow-renderer/src/vulkan/abstraction/Image.cpp new file mode 100644 index 0000000..eea62b4 --- /dev/null +++ b/projs/shadow/shadow-engine/shadow-renderer/src/vulkan/abstraction/Image.cpp @@ -0,0 +1,439 @@ +#include "vlkx/vulkan/abstraction/Image.h" + +#include +#include +#include +#define STB_IMAGE_IMPLEMENTATION +#include "stb_image.h" +#include "vlkx/vulkan/VulkanModule.h" +#include "shadow/util/File.h" + +namespace vlkx { + struct ImageConfig { + explicit ImageConfig(bool readable = false) { + if (readable) { + tiling = VK_IMAGE_TILING_LINEAR; + layout = VK_IMAGE_LAYOUT_PREINITIALIZED; + } else { + tiling = VK_IMAGE_TILING_OPTIMAL; + layout = VK_IMAGE_LAYOUT_UNDEFINED; + } + } + + uint32_t mipping = 1; + uint32_t layers = 1; + VkSampleCountFlagBits samples = VK_SAMPLE_COUNT_1_BIT; + VkImageTiling tiling; + VkImageLayout layout; + }; + + struct ImageData { + ImageDescriptor::Dimension dimensions; + const char* data; + }; + + ImageData loadImage(const std::string& path, int wantedChannels) { + shadowutil::FileData* data = shadowutil::loadFile(path); + int width, height, channels; + + stbi_uc* stbData = stbi_load_from_memory(reinterpret_cast(data->data.data()), data->size, &width, &height, &channels, wantedChannels); + if (stbData == nullptr) + throw std::runtime_error("Unable to read image file " + std::string(path)); + + switch (channels) { + case 1: + case 4: + break; + + case 3: { + stbi_image_free(stbData); + stbData = stbi_load_from_memory(reinterpret_cast(data->data.data()), data->size, &width, &height, &channels, STBI_rgb_alpha); + break; + } + + default: + throw std::runtime_error("Trying to load image with unsupported number of channels: " + std::to_string(channels)); + } + + return {{ static_cast(width), static_cast(height), static_cast(channels) },reinterpret_cast(stbData) }; + } + + std::optional findFormatWith(const std::vector& formats, VkFormatFeatureFlags feature) { + for (const auto format : formats) { + VkFormatProperties props; + vkGetPhysicalDeviceFormatProperties(VulkanModule::getInstance()->getDevice()->physical, format, &props); + if ((props.optimalTilingFeatures & feature) == feature) return format; + } + + return std::nullopt; + } + + VkFormat findFormatForChannels(int channels, const std::vector& usages, bool highPrecision = false) { + switch (channels) { + case 1: { + VkFormat best, alternative; + if (highPrecision) { + best = VK_FORMAT_R16_SFLOAT; + alternative = VK_FORMAT_R16G16B16A16_SFLOAT; + } else { + best = VK_FORMAT_R8_UNORM; + alternative = VK_FORMAT_R8G8B8A8_UNORM; + } + + if (findFormatWith({ best }, VK_FORMAT_FEATURE_STORAGE_IMAGE_BIT).has_value()) + return best; + else + return alternative; + } + + case 4: + if (highPrecision) + return VK_FORMAT_R16G16B16A16_SFLOAT; + else + return VK_FORMAT_R8G8B8A8_UNORM; + default: + throw std::runtime_error("Attempting to find format for invalid channels (RGB images have 4 channels!)"); + } + } + + VkFormat findFormatForDepthStencil() { + const auto format = findFormatWith({ VK_FORMAT_D32_SFLOAT_S8_UINT, VK_FORMAT_D24_UNORM_S8_UINT }, VK_FORMAT_FEATURE_DEPTH_STENCIL_ATTACHMENT_BIT); + if (!format.has_value()) + throw std::runtime_error("Unable to find a format for a depth stencil image."); + return format.value(); + } + + VkSampleCountFlagBits getMaxSamples(VkSampleCountFlags samples) { + for (const auto count : { VK_SAMPLE_COUNT_64_BIT, VK_SAMPLE_COUNT_32_BIT, VK_SAMPLE_COUNT_16_BIT, VK_SAMPLE_COUNT_8_BIT, VK_SAMPLE_COUNT_4_BIT, VK_SAMPLE_COUNT_2_BIT, VK_SAMPLE_COUNT_1_BIT }) { + if (samples & count) + return count; + } + + throw std::runtime_error("Multisampling isn't supported?"); + } + + Buffer::Buffer() { + } + + ImageDescriptor Image::loadCubeFromDisk(const std::string& directory, const std::array &files, + bool flipY) { + stbi_set_flip_vertically_on_load(flipY); + auto firstImage = loadImage(directory + "/" + files[0], STBI_default); + const ImageDescriptor::Dimension& dim = firstImage.dimensions; + char* data = new char[dim.getSize() * 6]; // TODO: Figure out how to make this delete + memcpy(data, firstImage.data, dim.getSize()); + for (size_t i = 1; i < 6; i++) { + auto image = loadImage(directory + "/" + files[1], STBI_default); + if (!(image.dimensions.width == dim.width && image.dimensions.height == dim.height && image.dimensions.channels == dim.channels)) + throw std::runtime_error("Image " + std::to_string(i) + "(" + directory + "/" + files[i] + ") has different dimensions from the first image."); + + memcpy(data + i * dim.getSize(), image.data, dim.getSize()); + } + + stbi_set_flip_vertically_on_load(false); + return { ImageDescriptor::Type::Cubemap, firstImage.dimensions, data }; + } + + ImageDescriptor Image::loadSingleFromDisk(std::string path, bool flipY) { + stbi_set_flip_vertically_on_load(flipY); + auto image = loadImage(std::move(path), STBI_default); + stbi_set_flip_vertically_on_load(false); + + return { ImageDescriptor::Type::Single, image.dimensions, image.data }; + } + + TextureImage::Meta createTextureMeta(const ImageDescriptor& image, const std::vector& usages) { + return TextureImage::Meta { + image.getData(), usages, + findFormatForChannels(image.getChannels(), usages), + image.getWidth(), image.getHeight(), image.getChannels(), + }; + } + + VkTools::ManagedImage createImage(const ImageConfig& config, VkImageCreateFlags flags, VkFormat format, const VkExtent3D& extent, VkImageUsageFlags usage) { + const auto device = VulkanModule::getInstance()->getDevice(); + + uint32_t graphicsQueue = (uint32_t) device->getQueues().graphics; + VkImageCreateInfo info { + VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO, nullptr, + flags, VK_IMAGE_TYPE_2D, format, + extent, config.mipping, config.layers, config.samples, config.tiling, + usage, VK_SHARING_MODE_EXCLUSIVE, + 1, &graphicsQueue, config.layout + }; + + // Prepare the managed image + VkTools::ManagedImage image {}; + + // Set up image allocation + VmaAllocationCreateInfo allocateInfo = {}; + allocateInfo.usage = VMA_MEMORY_USAGE_GPU_ONLY; + + // Allocate + create the image + vmaCreateImage(VulkanModule::getInstance()->getAllocator(), &info, &allocateInfo, &image.image, &image.allocation, nullptr); + + return image; + } + + void waitForBarrier(const VkCommandBuffer& commands, const VkImageMemoryBarrier& barrier, const std::array& stages) { + vkCmdPipelineBarrier(commands, stages[0], stages[1], 0, 0, nullptr, 0, nullptr, 1, &barrier); + } + + void transitionImage(const VkImage& image, const ImageConfig& config, VkImageAspectFlags aspect, const std::array& layouts, const std::array& access, const std::array& stages) { + VkTools::immediateExecute([&](const VkCommandBuffer& commands) { + waitForBarrier(commands, + { + VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER, + nullptr, + access[0], access[1], + layouts[0], layouts[1], + VK_QUEUE_FAMILY_IGNORED, VK_QUEUE_FAMILY_IGNORED, + image, + { aspect, 0, config.mipping, 0, config.layers } + }, stages); + }, VulkanModule::getInstance()->getDevice()); + } + + inline VkOffset3D convertExtent(const VkExtent2D& extent) { + return VkOffset3D { static_cast(extent.width), static_cast(extent.height), 1 }; + } + + inline VkExtent3D expandExtent(const VkExtent2D& extent) { + return VkExtent3D { extent.width, extent.height, 1 }; + } + + std::vector getExtentForMipmaps(const VkExtent3D& extent) { + const int largest = std::max(extent.width, extent.height); + const int mipping = std::floor(std::log2(largest)); + std::vector extents(mipping); + VkExtent2D imageExt { extent.width, extent.height }; + for (size_t level = 0; level < mipping; ++level) { + imageExt.width = imageExt.width > 1 ? imageExt.width / 2 : 1; + imageExt.height = imageExt.height > 1 ? imageExt.height / 2 : 1; + extents[level] = imageExt; + } + + return extents; + } + + void generateMipmaps(const VkImage& image, VkFormat format, const VkExtent3D& extent, const std::vector& mipExtents) { + VkFormatProperties props; + vkGetPhysicalDeviceFormatProperties(VulkanModule::getInstance()->getDevice()->physical, format, &props); + if (!(props.optimalTilingFeatures & VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT)) + throw std::runtime_error("Attempting to create Mipmaps for an image format that does not support blitting"); + + uint32_t destLevel = 1; + VkExtent2D previousExt { extent.width, extent.height }; + + VkTools::immediateExecute([&](const VkCommandBuffer& commands) { + // Blit the new images into place + for (const auto &ext : mipExtents) { + const uint32_t sourceLevel = destLevel - 1; + VkImageMemoryBarrier barrier { + VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER, + nullptr, VK_ACCESS_TRANSFER_WRITE_BIT, VK_ACCESS_TRANSFER_READ_BIT, + VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, + 0, 0, image, { VK_IMAGE_ASPECT_COLOR_BIT, sourceLevel, 1, 0, 1 } + }; + + waitForBarrier(commands, barrier, {VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT}); + + const VkImageBlit blit { + {VK_IMAGE_ASPECT_COLOR_BIT, sourceLevel, 0, 1}, + {{0, 0, 0}, convertExtent(previousExt)}, + {VK_IMAGE_ASPECT_COLOR_BIT, destLevel, 0, 1}, + {{0, 0, 0}, convertExtent(ext)} + }; + vkCmdBlitImage(commands, image, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, image, + VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, &blit, VK_FILTER_LINEAR); + + ++destLevel; + previousExt = ext; + } + }, VulkanModule::getInstance()->getDevice()); + + VkTools::immediateExecute([&](const VkCommandBuffer& commands) { + // Convert all images to shader read only so we can sample them + for (uint32_t level = 0; level < mipExtents.size() + 1; ++level) { + VkImageMemoryBarrier barrier { + VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER, + nullptr, VK_ACCESS_TRANSFER_WRITE_BIT, VK_ACCESS_TRANSFER_READ_BIT, + level == mipExtents.size() ? VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL : VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, + 0, 0, image, { VK_IMAGE_ASPECT_COLOR_BIT, level, 1, 0, 1 } + }; + + waitForBarrier(commands, barrier, { VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT }); + } + }, VulkanModule::getInstance()->getDevice()); + } + + Image::Image(const VkExtent2D &ext, VkFormat form) : extent(ext), format(form) { + dev = VulkanModule::getInstance()->getDevice(); + } + + void ImageStagingBuffer::copy(const VkImage &target, const VkExtent3D &extent, uint32_t layers) const { + VkTools::immediateExecute([&](const VkCommandBuffer& commands) { + const VkBufferImageCopy copyData { 0, 0, 0, { VK_IMAGE_ASPECT_COLOR_BIT, 0, 0, layers }, { 0, 0, 0 }, extent }; + vkCmdCopyBufferToImage(commands, getBuffer(), target, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, ©Data); + }, VulkanModule::getInstance()->getDevice()); + } + + ImageSampler::ImageSampler(int mipLevels, const vlkx::ImageSampler::Config &config) + : sampler(VkTools::createSampler(config.filter, config.mode, mipLevels, VulkanModule::getInstance()->getDevice()->logical)) { + dev = VulkanModule::getInstance()->getDevice(); + } + + Buffer::BulkCopyMeta TextureImage::Meta::getCopyMeta() const { + const VkDeviceSize singleSize = width * height * channels; + const VkDeviceSize totalSize = singleSize * data.size(); + std::vector copy(data.size()); + + // If we're making a framebuffer, we have no data to copy in. + if (data[0] == nullptr) + return { totalSize, {} }; + + for (size_t i = 0; i < copy.size(); ++i) { + copy[i] = { data[i], singleSize, singleSize * i }; + } + + return { totalSize, std::move(copy) }; + } + + TextureImage::TextureImage(bool mipmapping, const ImageSampler::Config &samplerConfig, const vlkx::TextureImage::Meta &meta) + : Image(meta.getExtent(), meta.format), buffer(mipmapping, meta), sampler(buffer.getMipping(), samplerConfig) { + + setView(VkTools::createImageView(buffer.getImage(), format, VK_IMAGE_ASPECT_COLOR_BIT, buffer.getMipping(), meta.data.size(), VulkanModule::getInstance()->getDevice()->logical)); + } + + TextureImage::TextureImage(bool mipmapping, const ImageDescriptor &image, const std::vector& usages, const ImageSampler::Config &config) + : TextureImage(mipmapping, config, createTextureMeta(image, usages)) + {} + + TextureImage::TextureBuffer::TextureBuffer(bool mipmaps, const vlkx::TextureImage::Meta &meta) : ImageBuffer() { + const VkExtent3D extent = expandExtent(meta.getExtent()); + const auto layers = meta.data.size(); + if (layers != 1 && layers != 6) + throw std::runtime_error("Attempting to allocate a texture buffer for an invalid number of textures; only single textures and cubemap textures are supported."); + + ImageConfig config; + config.layers = meta.data.size(); + + std::vector mipExtents; + if (mipmaps) { + mipExtents = getExtentForMipmaps(extent); + mipLevels = mipExtents.size() + 1; + } + + config.mipping = mipLevels; + + VkImageCreateFlags createFlags {}; + if (layers == 6) + createFlags |= VK_IMAGE_CREATE_CUBE_COMPATIBLE_BIT; + + auto usage = ImageUsage::getFlagsForUsage(meta.usages); + usage |= VK_IMAGE_USAGE_SAMPLED_BIT; + usage |= VK_IMAGE_USAGE_TRANSFER_DST_BIT; + if (mipmaps) usage |= VK_IMAGE_USAGE_TRANSFER_SRC_BIT; + + setImage(createImage(config, createFlags, meta.format, extent, usage)); + + transitionImage(getImage(), config, VK_IMAGE_ASPECT_COLOR_BIT, { VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL }, { 0, VK_ACCESS_TRANSFER_WRITE_BIT }, { VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT }); + + const ImageStagingBuffer staging(meta.getCopyMeta()); + staging.copy(getImage(), extent, config.layers); + + if (mipmaps) { + generateMipmaps(getImage(), meta.format, extent, mipExtents); + } else { + transitionImage(getImage(), config, VK_IMAGE_ASPECT_COLOR_BIT, { VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL }, { VK_ACCESS_TRANSFER_WRITE_BIT, VK_ACCESS_SHADER_READ_BIT }, { VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT }); + } + } + + RefCountedTexture::ReferenceCounter RefCountedTexture::get(const vlkx::RefCountedTexture::ImageLocation &location, const std::vector& usages, const ImageSampler::Config &config) { + bool mips; + const std::string* ident; + std::unique_ptr image; + + if (const auto* singleTex = std::get_if(&location); singleTex != nullptr) { + mips = true; + ident = singleTex; + image = std::make_unique(Image::loadSingleFromDisk(*singleTex, false)); + } else if (const auto* cubeTex = std::get_if(&location); cubeTex != nullptr) { + mips = false; + ident = &cubeTex->directory; + image = std::make_unique(Image::loadCubeFromDisk(cubeTex->directory, cubeTex->files, false)); + } + + return ReferenceCounter::get(*ident, mips, config, createTextureMeta(*image, usages)); + } + + DepthStencilImage::DepthStencilImage(const VkExtent2D &extent) : Image(extent, findFormatForDepthStencil()), buffer(extent, format) { + setView(VkTools::createImageView(getImage(), format, VK_IMAGE_ASPECT_DEPTH_BIT | VK_IMAGE_ASPECT_STENCIL_BIT, 1, 1, VulkanModule::getInstance()->getDevice()->logical)); + } + + DepthStencilImage::DepthStencilBuffer::DepthStencilBuffer(const VkExtent2D &extent, VkFormat format) : ImageBuffer() { + setImage(createImage(ImageConfig {}, 0, format, expandExtent(extent), VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT)); + } + + SwapchainImage::SwapchainImage(const VkImage &image, const VkExtent2D &extent, VkFormat format) : Image(extent, format), image(image) { + setView(VkTools::createImageView(image, format, VK_IMAGE_ASPECT_COLOR_BIT, 1, 1, VulkanModule::getInstance()->getDevice()->logical)); + managed = { image, nullptr }; + } + + std::unique_ptr MultisampleImage::createColor(const vlkx::Image &targetImage, vlkx::MultisampleImage::Mode mode) { + return std::unique_ptr(new MultisampleImage(targetImage.getExtent(), targetImage.getFormat(), mode, MultisampleBuffer::Type::Color)); + } + + std::unique_ptr MultisampleImage::createDepthStencilMS(const VkExtent2D &extent, vlkx::MultisampleImage::Mode mode) { + return std::unique_ptr(new MultisampleImage(extent, findFormatForDepthStencil(), mode, MultisampleBuffer::Type::DepthStencil)); + } + + std::unique_ptr MultisampleImage::createDepthStencil(VkExtent2D &extent, std::optional mode) { + if (mode.has_value()) + return createDepthStencilMS(extent, mode.value()); + else + return std::make_unique(extent); + } + + MultisampleImage::MultisampleImage(const VkExtent2D &extent, VkFormat format, vlkx::MultisampleImage::Mode mode,MultisampleBuffer::Type type) + : Image(extent, format), samples(chooseSamples(mode)), buffer(type, extent, format, samples) { + + VkImageAspectFlags aspect; + switch (type) { + case MultisampleBuffer::Type::Color: aspect = VK_IMAGE_ASPECT_COLOR_BIT; break; + case MultisampleBuffer::Type::DepthStencil: aspect = VK_IMAGE_ASPECT_DEPTH_BIT | VK_IMAGE_ASPECT_STENCIL_BIT; break; + } + setView(VkTools::createImageView(buffer.getImage(), format, aspect, 1, 1, VulkanModule::getInstance()->getDevice()->logical)); + } + + VkSampleCountFlagBits MultisampleImage::chooseSamples(vlkx::MultisampleImage::Mode mode) { + VkPhysicalDeviceLimits limits; + VkPhysicalDeviceProperties props; + vkGetPhysicalDeviceProperties(VulkanModule::getInstance()->getDevice()->physical, &props); + limits = props.limits; + + const VkSampleCountFlags sampleFlags = std::min({ limits.framebufferColorSampleCounts, limits.framebufferDepthSampleCounts, limits.framebufferStencilSampleCounts }); + const VkSampleCountFlagBits maxSamples = getMaxSamples(sampleFlags); + switch (mode) { + case Mode::MostEfficient: return std::min(VK_SAMPLE_COUNT_4_BIT, maxSamples); + case Mode::Highest: return maxSamples; + } + } + + MultisampleImage::MultisampleBuffer::MultisampleBuffer(vlkx::MultisampleImage::MultisampleBuffer::Type type, const VkExtent2D &extent, VkFormat format, VkSampleCountFlagBits samples) + : ImageBuffer() { + + VkImageUsageFlags usageFlags; + switch (type) { + case Type::Color: usageFlags = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | VK_IMAGE_USAGE_TRANSIENT_ATTACHMENT_BIT; break; + case Type::DepthStencil: usageFlags = VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT; break; + } + + ImageConfig config; + config.samples = samples; + + setImage(createImage(config, 0, format, expandExtent(extent), usageFlags)); + + } + +} \ No newline at end of file diff --git a/projs/shadow/shadow-engine/shadow-utility/inc/shadow/util/File.h b/projs/shadow/shadow-engine/shadow-utility/inc/shadow/util/File.h new file mode 100644 index 0000000..14a94ac --- /dev/null +++ b/projs/shadow/shadow-engine/shadow-utility/inc/shadow/util/File.h @@ -0,0 +1,13 @@ +#pragma once +#include +#include +namespace shadowutil { + + struct FileData { + size_t size; + std::vector data; + }; + + // A testing stub; this should be deleted and wired into the asset system once that becomes ready. + FileData* loadFile(std::string path); +} \ No newline at end of file diff --git a/projs/shadow/shadow-engine/shadow-utility/inc/shadow/util/RefCounter.h b/projs/shadow/shadow-engine/shadow-utility/inc/shadow/util/RefCounter.h new file mode 100644 index 0000000..87400dd --- /dev/null +++ b/projs/shadow/shadow-engine/shadow-utility/inc/shadow/util/RefCounter.h @@ -0,0 +1,121 @@ +#pragma once + +#include +#include +#include + +namespace shadowutil { + + // An object wrapper that behaves like smart pointers. + // References to this object are counted, and the object will be destructed when there are no references. + // This allows for automatic, safe and leak-free handling of all kinds of resources. + // The AutoRelease behaviour can be adjusted. + template + class RefCounter { + public: + + // Preserves ObjectType instances in the current scope. + // Use like lock_guard; RAII allows precise scoping rules. + class AutoRelease { + public: + explicit AutoRelease() { RefCounter::registerAutoRelease(); } + + AutoRelease(const AutoRelease&) = delete; + AutoRelease& operator=(const AutoRelease&) = delete; + + ~AutoRelease() { RefCounter::unregisterAutoRelease(); } + + // AutoRelease can only exist on the stack. + void* operator new(size_t) = delete; + void* operator new[](size_t) = delete; + }; + + // Use to access a ref counted object. + // If exists, the object will be passed and reference counter increased. + // Otherwise, args will be used to create. + template + static RefCounter get(const std::string& identifier, Args&&... args) { + auto iter = getMap().find(identifier); + if (iter == getMap().end()) { + const auto inserted = getMap().insert( + { + identifier, + typename ObjectPool::CountedObject { + std::make_unique(std::forward(args)...), 0 + } + } + ); + iter = inserted.first; + } + + auto& object = iter->second; + ++object.references; + return RefCounter { identifier, object.obj.get() }; + } + + RefCounter(RefCounter&& other) noexcept { + identifier = std::move(other.identifier); + objectPtr = other.objectPtr; + other.identifier.clear(); + } + + RefCounter& operator=(RefCounter&& other) noexcept { + std::swap(identifier, other.identifier); + std::swap(objectPtr, other.objectPtr); + return *this; + } + + // When we reach 0 references and there's no auto release system set up, destroy the object. + ~RefCounter() { + if (identifier.empty()) return; + + const auto iter = getMap().find(identifier); + if (--iter->second.references == 0 && objectPool.activePools == 0) + getMap().erase(iter); + } + + // Smart pointer emulation overloads. + const ObjectType* operator->() const { return objectPtr; } + const ObjectType& operator*() const { return *objectPtr; } + + static bool hasAutoRelease() { return objectPool.activePools != 0; } + + private: + // The object pool that handles managing and counting objects. + struct ObjectPool { + struct CountedObject { + std::unique_ptr obj; + size_t references; + }; + + using RefCountMap = std::map; + + RefCountMap refCountMap; + size_t activePools = 0; + }; + + RefCounter(std::string identifier, const ObjectType* obj) : identifier(std::move(identifier)), objectPtr(obj) {} + + static void unregisterAutoRelease() { + if (--objectPool.activePools == 0) { + using CountedObject = typename ObjectPool::CountedObject; + static const auto removeZeroRef = [](const std::pair &pair) { + return pair.second.references == 0; + }; + std::erase_if(getMap(), removeZeroRef); + } + } + + static void registerAutoRelease() { ++objectPool.activePools; } + + static typename ObjectPool::RefCountMap& getMap() { return objectPool.refCountMap; } + + static ObjectPool objectPool; + + std::string identifier; + const ObjectType* objectPtr; + }; + + template + typename RefCounter::ObjectPool RefCounter::objectPool {}; +} \ No newline at end of file diff --git a/projs/shadow/shadow-engine/shadow-utility/inc/stb_image.h b/projs/shadow/shadow-engine/shadow-utility/inc/stb_image.h new file mode 100644 index 0000000..622061a --- /dev/null +++ b/projs/shadow/shadow-engine/shadow-utility/inc/stb_image.h @@ -0,0 +1,7897 @@ +/* stb_image - v2.27 - public domain image loader - http://nothings.org/stb + no warranty implied; use at your own risk + + Do this: + #define STB_IMAGE_IMPLEMENTATION + before you include this file in *one* C or C++ file to create the implementation. + + // i.e. it should look like this: + #include ... + #include ... + #include ... + #define STB_IMAGE_IMPLEMENTATION + #include "stb_image.h" + + You can #define STBI_ASSERT(x) before the #include to avoid using assert.h. + And #define STBI_MALLOC, STBI_REALLOC, and STBI_FREE to avoid using malloc,realloc,free + + + QUICK NOTES: + Primarily of interest to game developers and other people who can + avoid problematic images and only need the trivial interface + + JPEG baseline & progressive (12 bpc/arithmetic not supported, same as stock IJG lib) + PNG 1/2/4/8/16-bit-per-channel + + TGA (not sure what subset, if a subset) + BMP non-1bpp, non-RLE + PSD (composited view only, no extra channels, 8/16 bit-per-channel) + + GIF (*comp always reports as 4-channel) + HDR (radiance rgbE format) + PIC (Softimage PIC) + PNM (PPM and PGM binary only) + + Animated GIF still needs a proper API, but here's one way to do it: + http://gist.github.com/urraka/685d9a6340b26b830d49 + + - decode from memory or through FILE (define STBI_NO_STDIO to remove code) + - decode from arbitrary I/O callbacks + - SIMD acceleration on x86/x64 (SSE2) and ARM (NEON) + + Full documentation under "DOCUMENTATION" below. + + +LICENSE + + See end of file for license information. + +RECENT REVISION HISTORY: + + 2.27 (2021-07-11) document stbi_info better, 16-bit PNM support, bug fixes + 2.26 (2020-07-13) many minor fixes + 2.25 (2020-02-02) fix warnings + 2.24 (2020-02-02) fix warnings; thread-local failure_reason and flip_vertically + 2.23 (2019-08-11) fix clang static analysis warning + 2.22 (2019-03-04) gif fixes, fix warnings + 2.21 (2019-02-25) fix typo in comment + 2.20 (2019-02-07) support utf8 filenames in Windows; fix warnings and platform ifdefs + 2.19 (2018-02-11) fix warning + 2.18 (2018-01-30) fix warnings + 2.17 (2018-01-29) bugfix, 1-bit BMP, 16-bitness query, fix warnings + 2.16 (2017-07-23) all functions have 16-bit variants; optimizations; bugfixes + 2.15 (2017-03-18) fix png-1,2,4; all Imagenet JPGs; no runtime SSE detection on GCC + 2.14 (2017-03-03) remove deprecated STBI_JPEG_OLD; fixes for Imagenet JPGs + 2.13 (2016-12-04) experimental 16-bit API, only for PNG so far; fixes + 2.12 (2016-04-02) fix typo in 2.11 PSD fix that caused crashes + 2.11 (2016-04-02) 16-bit PNGS; enable SSE2 in non-gcc x64 + RGB-format JPEG; remove white matting in PSD; + allocate large structures on the stack; + correct channel count for PNG & BMP + 2.10 (2016-01-22) avoid warning introduced in 2.09 + 2.09 (2016-01-16) 16-bit TGA; comments in PNM files; STBI_REALLOC_SIZED + + See end of file for full revision history. + + + ============================ Contributors ========================= + + Image formats Extensions, features + Sean Barrett (jpeg, png, bmp) Jetro Lauha (stbi_info) + Nicolas Schulz (hdr, psd) Martin "SpartanJ" Golini (stbi_info) + Jonathan Dummer (tga) James "moose2000" Brown (iPhone PNG) + Jean-Marc Lienher (gif) Ben "Disch" Wenger (io callbacks) + Tom Seddon (pic) Omar Cornut (1/2/4-bit PNG) + Thatcher Ulrich (psd) Nicolas Guillemot (vertical flip) + Ken Miller (pgm, ppm) Richard Mitton (16-bit PSD) + github:urraka (animated gif) Junggon Kim (PNM comments) + Christopher Forseth (animated gif) Daniel Gibson (16-bit TGA) + socks-the-fox (16-bit PNG) + Jeremy Sawicki (handle all ImageNet JPGs) + Optimizations & bugfixes Mikhail Morozov (1-bit BMP) + Fabian "ryg" Giesen Anael Seghezzi (is-16-bit query) + Arseny Kapoulkine Simon Breuss (16-bit PNM) + John-Mark Allen + Carmelo J Fdez-Aguera + + Bug & warning fixes + Marc LeBlanc David Woo Guillaume George Martins Mozeiko + Christpher Lloyd Jerry Jansson Joseph Thomson Blazej Dariusz Roszkowski + Phil Jordan Dave Moore Roy Eltham + Hayaki Saito Nathan Reed Won Chun + Luke Graham Johan Duparc Nick Verigakis the Horde3D community + Thomas Ruf Ronny Chevalier github:rlyeh + Janez Zemva John Bartholomew Michal Cichon github:romigrou + Jonathan Blow Ken Hamada Tero Hanninen github:svdijk + Eugene Golushkov Laurent Gomila Cort Stratton github:snagar + Aruelien Pocheville Sergio Gonzalez Thibault Reuille github:Zelex + Cass Everitt Ryamond Barbiero github:grim210 + Paul Du Bois Engin Manap Aldo Culquicondor github:sammyhw + Philipp Wiesemann Dale Weiler Oriol Ferrer Mesia github:phprus + Josh Tobin Matthew Gregan github:poppolopoppo + Julian Raschke Gregory Mullen Christian Floisand github:darealshinji + Baldur Karlsson Kevin Schmidt JR Smith github:Michaelangel007 + Brad Weinberger Matvey Cherevko github:mosra + Luca Sas Alexander Veselov Zack Middleton [reserved] + Ryan C. Gordon [reserved] [reserved] + DO NOT ADD YOUR NAME HERE + + Jacko Dirks + + To add your name to the credits, pick a random blank space in the middle and fill it. + 80% of merge conflicts on stb PRs are due to people adding their name at the end + of the credits. +*/ + +#ifndef STBI_INCLUDE_STB_IMAGE_H +#define STBI_INCLUDE_STB_IMAGE_H + +// DOCUMENTATION +// +// Limitations: +// - no 12-bit-per-channel JPEG +// - no JPEGs with arithmetic coding +// - GIF always returns *comp=4 +// +// Basic usage (see HDR discussion below for HDR usage): +// int x,y,n; +// unsigned char *data = stbi_load(filename, &x, &y, &n, 0); +// // ... process data if not NULL ... +// // ... x = width, y = height, n = # 8-bit components per pixel ... +// // ... replace '0' with '1'..'4' to force that many components per pixel +// // ... but 'n' will always be the number that it would have been if you said 0 +// stbi_image_free(data) +// +// Standard parameters: +// int *x -- outputs image width in pixels +// int *y -- outputs image height in pixels +// int *channels_in_file -- outputs # of image components in image file +// int desired_channels -- if non-zero, # of image components requested in result +// +// The return value from an image loader is an 'unsigned char *' which points +// to the pixel data, or NULL on an allocation failure or if the image is +// corrupt or invalid. The pixel data consists of *y scanlines of *x pixels, +// with each pixel consisting of N interleaved 8-bit components; the first +// pixel pointed to is top-left-most in the image. There is no padding between +// image scanlines or between pixels, regardless of format. The number of +// components N is 'desired_channels' if desired_channels is non-zero, or +// *channels_in_file otherwise. If desired_channels is non-zero, +// *channels_in_file has the number of components that _would_ have been +// output otherwise. E.g. if you set desired_channels to 4, you will always +// get RGBA output, but you can check *channels_in_file to see if it's trivially +// opaque because e.g. there were only 3 channels in the source image. +// +// An output image with N components has the following components interleaved +// in this order in each pixel: +// +// N=#comp components +// 1 grey +// 2 grey, alpha +// 3 red, green, blue +// 4 red, green, blue, alpha +// +// If image loading fails for any reason, the return value will be NULL, +// and *x, *y, *channels_in_file will be unchanged. The function +// stbi_failure_reason() can be queried for an extremely brief, end-user +// unfriendly explanation of why the load failed. Define STBI_NO_FAILURE_STRINGS +// to avoid compiling these strings at all, and STBI_FAILURE_USERMSG to get slightly +// more user-friendly ones. +// +// Paletted PNG, BMP, GIF, and PIC images are automatically depalettized. +// +// To query the width, height and component count of an image without having to +// decode the full file, you can use the stbi_info family of functions: +// +// int x,y,n,ok; +// ok = stbi_info(filename, &x, &y, &n); +// // returns ok=1 and sets x, y, n if image is a supported format, +// // 0 otherwise. +// +// Note that stb_image pervasively uses ints in its public API for sizes, +// including sizes of memory buffers. This is now part of the API and thus +// hard to change without causing breakage. As a result, the various image +// loaders all have certain limits on image size; these differ somewhat +// by format but generally boil down to either just under 2GB or just under +// 1GB. When the decoded image would be larger than this, stb_image decoding +// will fail. +// +// Additionally, stb_image will reject image files that have any of their +// dimensions set to a larger value than the configurable STBI_MAX_DIMENSIONS, +// which defaults to 2**24 = 16777216 pixels. Due to the above memory limit, +// the only way to have an image with such dimensions load correctly +// is for it to have a rather extreme aspect ratio. Either way, the +// assumption here is that such larger images are likely to be malformed +// or malicious. If you do need to load an image with individual dimensions +// larger than that, and it still fits in the overall size limit, you can +// #define STBI_MAX_DIMENSIONS on your own to be something larger. +// +// =========================================================================== +// +// UNICODE: +// +// If compiling for Windows and you wish to use Unicode filenames, compile +// with +// #define STBI_WINDOWS_UTF8 +// and pass utf8-encoded filenames. Call stbi_convert_wchar_to_utf8 to convert +// Windows wchar_t filenames to utf8. +// +// =========================================================================== +// +// Philosophy +// +// stb libraries are designed with the following priorities: +// +// 1. easy to use +// 2. easy to maintain +// 3. good performance +// +// Sometimes I let "good performance" creep up in priority over "easy to maintain", +// and for best performance I may provide less-easy-to-use APIs that give higher +// performance, in addition to the easy-to-use ones. Nevertheless, it's important +// to keep in mind that from the standpoint of you, a client of this library, +// all you care about is #1 and #3, and stb libraries DO NOT emphasize #3 above all. +// +// Some secondary priorities arise directly from the first two, some of which +// provide more explicit reasons why performance can't be emphasized. +// +// - Portable ("ease of use") +// - Small source code footprint ("easy to maintain") +// - No dependencies ("ease of use") +// +// =========================================================================== +// +// I/O callbacks +// +// I/O callbacks allow you to read from arbitrary sources, like packaged +// files or some other source. Data read from callbacks are processed +// through a small internal buffer (currently 128 bytes) to try to reduce +// overhead. +// +// The three functions you must define are "read" (reads some bytes of data), +// "skip" (skips some bytes of data), "eof" (reports if the stream is at the end). +// +// =========================================================================== +// +// SIMD support +// +// The JPEG decoder will try to automatically use SIMD kernels on x86 when +// supported by the compiler. For ARM Neon support, you must explicitly +// request it. +// +// (The old do-it-yourself SIMD API is no longer supported in the current +// code.) +// +// On x86, SSE2 will automatically be used when available based on a run-time +// test; if not, the generic C versions are used as a fall-back. On ARM targets, +// the typical path is to have separate builds for NEON and non-NEON devices +// (at least this is true for iOS and Android). Therefore, the NEON support is +// toggled by a build flag: define STBI_NEON to get NEON loops. +// +// If for some reason you do not want to use any of SIMD code, or if +// you have issues compiling it, you can disable it entirely by +// defining STBI_NO_SIMD. +// +// =========================================================================== +// +// HDR image support (disable by defining STBI_NO_HDR) +// +// stb_image supports loading HDR images in general, and currently the Radiance +// .HDR file format specifically. You can still load any file through the existing +// interface; if you attempt to load an HDR file, it will be automatically remapped +// to LDR, assuming gamma 2.2 and an arbitrary scale factor defaulting to 1; +// both of these constants can be reconfigured through this interface: +// +// stbi_hdr_to_ldr_gamma(2.2f); +// stbi_hdr_to_ldr_scale(1.0f); +// +// (note, do not use _inverse_ constants; stbi_image will invert them +// appropriately). +// +// Additionally, there is a new, parallel interface for loading files as +// (linear) floats to preserve the full dynamic range: +// +// float *data = stbi_loadf(filename, &x, &y, &n, 0); +// +// If you load LDR images through this interface, those images will +// be promoted to floating point values, run through the inverse of +// constants corresponding to the above: +// +// stbi_ldr_to_hdr_scale(1.0f); +// stbi_ldr_to_hdr_gamma(2.2f); +// +// Finally, given a filename (or an open file or memory block--see header +// file for details) containing image data, you can query for the "most +// appropriate" interface to use (that is, whether the image is HDR or +// not), using: +// +// stbi_is_hdr(char *filename); +// +// =========================================================================== +// +// iPhone PNG support: +// +// We optionally support converting iPhone-formatted PNGs (which store +// premultiplied BGRA) back to RGB, even though they're internally encoded +// differently. To enable this conversion, call +// stbi_convert_iphone_png_to_rgb(1). +// +// Call stbi_set_unpremultiply_on_load(1) as well to force a divide per +// pixel to remove any premultiplied alpha *only* if the image file explicitly +// says there's premultiplied data (currently only happens in iPhone images, +// and only if iPhone convert-to-rgb processing is on). +// +// =========================================================================== +// +// ADDITIONAL CONFIGURATION +// +// - You can suppress implementation of any of the decoders to reduce +// your code footprint by #defining one or more of the following +// symbols before creating the implementation. +// +// STBI_NO_JPEG +// STBI_NO_PNG +// STBI_NO_BMP +// STBI_NO_PSD +// STBI_NO_TGA +// STBI_NO_GIF +// STBI_NO_HDR +// STBI_NO_PIC +// STBI_NO_PNM (.ppm and .pgm) +// +// - You can request *only* certain decoders and suppress all other ones +// (this will be more forward-compatible, as addition of new decoders +// doesn't require you to disable them explicitly): +// +// STBI_ONLY_JPEG +// STBI_ONLY_PNG +// STBI_ONLY_BMP +// STBI_ONLY_PSD +// STBI_ONLY_TGA +// STBI_ONLY_GIF +// STBI_ONLY_HDR +// STBI_ONLY_PIC +// STBI_ONLY_PNM (.ppm and .pgm) +// +// - If you use STBI_NO_PNG (or _ONLY_ without PNG), and you still +// want the zlib decoder to be available, #define STBI_SUPPORT_ZLIB +// +// - If you define STBI_MAX_DIMENSIONS, stb_image will reject images greater +// than that size (in either width or height) without further processing. +// This is to let programs in the wild set an upper bound to prevent +// denial-of-service attacks on untrusted data, as one could generate a +// valid image of gigantic dimensions and force stb_image to allocate a +// huge block of memory and spend disproportionate time decoding it. By +// default this is set to (1 << 24), which is 16777216, but that's still +// very big. + +#ifndef STBI_NO_STDIO +#include +#endif // STBI_NO_STDIO + +#define STBI_VERSION 1 + +enum +{ + STBI_default = 0, // only used for desired_channels + + STBI_grey = 1, + STBI_grey_alpha = 2, + STBI_rgb = 3, + STBI_rgb_alpha = 4 +}; + +#include +typedef unsigned char stbi_uc; +typedef unsigned short stbi_us; + +#ifdef __cplusplus +extern "C" { +#endif + +#ifndef STBIDEF +#ifdef STB_IMAGE_STATIC +#define STBIDEF static +#else +#define STBIDEF extern +#endif +#endif + +////////////////////////////////////////////////////////////////////////////// +// +// PRIMARY API - works on images of any type +// + +// +// load image by filename, open file, or memory buffer +// + +typedef struct +{ + int (*read) (void *user,char *data,int size); // fill 'data' with 'size' bytes. return number of bytes actually read + void (*skip) (void *user,int n); // skip the next 'n' bytes, or 'unget' the last -n bytes if negative + int (*eof) (void *user); // returns nonzero if we are at end of file/data +} stbi_io_callbacks; + +//////////////////////////////////// +// +// 8-bits-per-channel interface +// + +STBIDEF stbi_uc *stbi_load_from_memory (stbi_uc const *buffer, int len , int *x, int *y, int *channels_in_file, int desired_channels); +STBIDEF stbi_uc *stbi_load_from_callbacks(stbi_io_callbacks const *clbk , void *user, int *x, int *y, int *channels_in_file, int desired_channels); + +#ifndef STBI_NO_STDIO +STBIDEF stbi_uc *stbi_load (char const *filename, int *x, int *y, int *channels_in_file, int desired_channels); +STBIDEF stbi_uc *stbi_load_from_file (FILE *f, int *x, int *y, int *channels_in_file, int desired_channels); +// for stbi_load_from_file, file pointer is left pointing immediately after image +#endif + +#ifndef STBI_NO_GIF +STBIDEF stbi_uc *stbi_load_gif_from_memory(stbi_uc const *buffer, int len, int **delays, int *x, int *y, int *z, int *comp, int req_comp); +#endif + +#ifdef STBI_WINDOWS_UTF8 +STBIDEF int stbi_convert_wchar_to_utf8(char *buffer, size_t bufferlen, const wchar_t* input); +#endif + +//////////////////////////////////// +// +// 16-bits-per-channel interface +// + +STBIDEF stbi_us *stbi_load_16_from_memory (stbi_uc const *buffer, int len, int *x, int *y, int *channels_in_file, int desired_channels); +STBIDEF stbi_us *stbi_load_16_from_callbacks(stbi_io_callbacks const *clbk, void *user, int *x, int *y, int *channels_in_file, int desired_channels); + +#ifndef STBI_NO_STDIO +STBIDEF stbi_us *stbi_load_16 (char const *filename, int *x, int *y, int *channels_in_file, int desired_channels); +STBIDEF stbi_us *stbi_load_from_file_16(FILE *f, int *x, int *y, int *channels_in_file, int desired_channels); +#endif + +//////////////////////////////////// +// +// float-per-channel interface +// +#ifndef STBI_NO_LINEAR +STBIDEF float *stbi_loadf_from_memory (stbi_uc const *buffer, int len, int *x, int *y, int *channels_in_file, int desired_channels); +STBIDEF float *stbi_loadf_from_callbacks (stbi_io_callbacks const *clbk, void *user, int *x, int *y, int *channels_in_file, int desired_channels); + +#ifndef STBI_NO_STDIO +STBIDEF float *stbi_loadf (char const *filename, int *x, int *y, int *channels_in_file, int desired_channels); +STBIDEF float *stbi_loadf_from_file (FILE *f, int *x, int *y, int *channels_in_file, int desired_channels); +#endif +#endif + +#ifndef STBI_NO_HDR +STBIDEF void stbi_hdr_to_ldr_gamma(float gamma); +STBIDEF void stbi_hdr_to_ldr_scale(float scale); +#endif // STBI_NO_HDR + +#ifndef STBI_NO_LINEAR +STBIDEF void stbi_ldr_to_hdr_gamma(float gamma); +STBIDEF void stbi_ldr_to_hdr_scale(float scale); +#endif // STBI_NO_LINEAR + +// stbi_is_hdr is always defined, but always returns false if STBI_NO_HDR +STBIDEF int stbi_is_hdr_from_callbacks(stbi_io_callbacks const *clbk, void *user); +STBIDEF int stbi_is_hdr_from_memory(stbi_uc const *buffer, int len); +#ifndef STBI_NO_STDIO +STBIDEF int stbi_is_hdr (char const *filename); +STBIDEF int stbi_is_hdr_from_file(FILE *f); +#endif // STBI_NO_STDIO + + +// get a VERY brief reason for failure +// on most compilers (and ALL modern mainstream compilers) this is threadsafe +STBIDEF const char *stbi_failure_reason (void); + +// free the loaded image -- this is just free() +STBIDEF void stbi_image_free (void *retval_from_stbi_load); + +// get image dimensions & components without fully decoding +STBIDEF int stbi_info_from_memory(stbi_uc const *buffer, int len, int *x, int *y, int *comp); +STBIDEF int stbi_info_from_callbacks(stbi_io_callbacks const *clbk, void *user, int *x, int *y, int *comp); +STBIDEF int stbi_is_16_bit_from_memory(stbi_uc const *buffer, int len); +STBIDEF int stbi_is_16_bit_from_callbacks(stbi_io_callbacks const *clbk, void *user); + +#ifndef STBI_NO_STDIO +STBIDEF int stbi_info (char const *filename, int *x, int *y, int *comp); +STBIDEF int stbi_info_from_file (FILE *f, int *x, int *y, int *comp); +STBIDEF int stbi_is_16_bit (char const *filename); +STBIDEF int stbi_is_16_bit_from_file(FILE *f); +#endif + + + +// for image formats that explicitly notate that they have premultiplied alpha, +// we just return the colors as stored in the file. set this flag to force +// unpremultiplication. results are undefined if the unpremultiply overflow. +STBIDEF void stbi_set_unpremultiply_on_load(int flag_true_if_should_unpremultiply); + +// indicate whether we should process iphone images back to canonical format, +// or just pass them through "as-is" +STBIDEF void stbi_convert_iphone_png_to_rgb(int flag_true_if_should_convert); + +// flip the image vertically, so the first pixel in the output array is the bottom left +STBIDEF void stbi_set_flip_vertically_on_load(int flag_true_if_should_flip); + +// as above, but only applies to images loaded on the thread that calls the function +// this function is only available if your compiler supports thread-local variables; +// calling it will fail to link if your compiler doesn't +STBIDEF void stbi_set_unpremultiply_on_load_thread(int flag_true_if_should_unpremultiply); +STBIDEF void stbi_convert_iphone_png_to_rgb_thread(int flag_true_if_should_convert); +STBIDEF void stbi_set_flip_vertically_on_load_thread(int flag_true_if_should_flip); + +// ZLIB client - used by PNG, available for other purposes + +STBIDEF char *stbi_zlib_decode_malloc_guesssize(const char *buffer, int len, int initial_size, int *outlen); +STBIDEF char *stbi_zlib_decode_malloc_guesssize_headerflag(const char *buffer, int len, int initial_size, int *outlen, int parse_header); +STBIDEF char *stbi_zlib_decode_malloc(const char *buffer, int len, int *outlen); +STBIDEF int stbi_zlib_decode_buffer(char *obuffer, int olen, const char *ibuffer, int ilen); + +STBIDEF char *stbi_zlib_decode_noheader_malloc(const char *buffer, int len, int *outlen); +STBIDEF int stbi_zlib_decode_noheader_buffer(char *obuffer, int olen, const char *ibuffer, int ilen); + + +#ifdef __cplusplus +} +#endif + +// +// +//// end header file ///////////////////////////////////////////////////// +#endif // STBI_INCLUDE_STB_IMAGE_H + +#ifdef STB_IMAGE_IMPLEMENTATION + +#if defined(STBI_ONLY_JPEG) || defined(STBI_ONLY_PNG) || defined(STBI_ONLY_BMP) \ + || defined(STBI_ONLY_TGA) || defined(STBI_ONLY_GIF) || defined(STBI_ONLY_PSD) \ + || defined(STBI_ONLY_HDR) || defined(STBI_ONLY_PIC) || defined(STBI_ONLY_PNM) \ + || defined(STBI_ONLY_ZLIB) + #ifndef STBI_ONLY_JPEG + #define STBI_NO_JPEG + #endif + #ifndef STBI_ONLY_PNG + #define STBI_NO_PNG + #endif + #ifndef STBI_ONLY_BMP + #define STBI_NO_BMP + #endif + #ifndef STBI_ONLY_PSD + #define STBI_NO_PSD + #endif + #ifndef STBI_ONLY_TGA + #define STBI_NO_TGA + #endif + #ifndef STBI_ONLY_GIF + #define STBI_NO_GIF + #endif + #ifndef STBI_ONLY_HDR + #define STBI_NO_HDR + #endif + #ifndef STBI_ONLY_PIC + #define STBI_NO_PIC + #endif + #ifndef STBI_ONLY_PNM + #define STBI_NO_PNM + #endif +#endif + +#if defined(STBI_NO_PNG) && !defined(STBI_SUPPORT_ZLIB) && !defined(STBI_NO_ZLIB) +#define STBI_NO_ZLIB +#endif + + +#include +#include // ptrdiff_t on osx +#include +#include +#include + +#if !defined(STBI_NO_LINEAR) || !defined(STBI_NO_HDR) +#include // ldexp, pow +#endif + +#ifndef STBI_NO_STDIO +#include +#endif + +#ifndef STBI_ASSERT +#include +#define STBI_ASSERT(x) assert(x) +#endif + +#ifdef __cplusplus +#define STBI_EXTERN extern "C" +#else +#define STBI_EXTERN extern +#endif + + +#ifndef _MSC_VER + #ifdef __cplusplus + #define stbi_inline inline + #else + #define stbi_inline + #endif +#else + #define stbi_inline __forceinline +#endif + +#ifndef STBI_NO_THREAD_LOCALS + #if defined(__cplusplus) && __cplusplus >= 201103L + #define STBI_THREAD_LOCAL thread_local + #elif defined(__GNUC__) && __GNUC__ < 5 + #define STBI_THREAD_LOCAL __thread + #elif defined(_MSC_VER) + #define STBI_THREAD_LOCAL __declspec(thread) + #elif defined (__STDC_VERSION__) && __STDC_VERSION__ >= 201112L && !defined(__STDC_NO_THREADS__) + #define STBI_THREAD_LOCAL _Thread_local + #endif + + #ifndef STBI_THREAD_LOCAL + #if defined(__GNUC__) + #define STBI_THREAD_LOCAL __thread + #endif + #endif +#endif + +#ifdef _MSC_VER +typedef unsigned short stbi__uint16; +typedef signed short stbi__int16; +typedef unsigned int stbi__uint32; +typedef signed int stbi__int32; +#else +#include +typedef uint16_t stbi__uint16; +typedef int16_t stbi__int16; +typedef uint32_t stbi__uint32; +typedef int32_t stbi__int32; +#endif + +// should produce compiler error if size is wrong +typedef unsigned char validate_uint32[sizeof(stbi__uint32)==4 ? 1 : -1]; + +#ifdef _MSC_VER +#define STBI_NOTUSED(v) (void)(v) +#else +#define STBI_NOTUSED(v) (void)sizeof(v) +#endif + +#ifdef _MSC_VER +#define STBI_HAS_LROTL +#endif + +#ifdef STBI_HAS_LROTL + #define stbi_lrot(x,y) _lrotl(x,y) +#else + #define stbi_lrot(x,y) (((x) << (y)) | ((x) >> (-(y) & 31))) +#endif + +#if defined(STBI_MALLOC) && defined(STBI_FREE) && (defined(STBI_REALLOC) || defined(STBI_REALLOC_SIZED)) +// ok +#elif !defined(STBI_MALLOC) && !defined(STBI_FREE) && !defined(STBI_REALLOC) && !defined(STBI_REALLOC_SIZED) +// ok +#else +#error "Must define all or none of STBI_MALLOC, STBI_FREE, and STBI_REALLOC (or STBI_REALLOC_SIZED)." +#endif + +#ifndef STBI_MALLOC +#define STBI_MALLOC(sz) malloc(sz) +#define STBI_REALLOC(p,newsz) realloc(p,newsz) +#define STBI_FREE(p) free(p) +#endif + +#ifndef STBI_REALLOC_SIZED +#define STBI_REALLOC_SIZED(p,oldsz,newsz) STBI_REALLOC(p,newsz) +#endif + +// x86/x64 detection +#if defined(__x86_64__) || defined(_M_X64) +#define STBI__X64_TARGET +#elif defined(__i386) || defined(_M_IX86) +#define STBI__X86_TARGET +#endif + +#if defined(__GNUC__) && defined(STBI__X86_TARGET) && !defined(__SSE2__) && !defined(STBI_NO_SIMD) +// gcc doesn't support sse2 intrinsics unless you compile with -msse2, +// which in turn means it gets to use SSE2 everywhere. This is unfortunate, +// but previous attempts to provide the SSE2 functions with runtime +// detection caused numerous issues. The way architecture extensions are +// exposed in GCC/Clang is, sadly, not really suited for one-file libs. +// New behavior: if compiled with -msse2, we use SSE2 without any +// detection; if not, we don't use it at all. +#define STBI_NO_SIMD +#endif + +#if defined(__MINGW32__) && defined(STBI__X86_TARGET) && !defined(STBI_MINGW_ENABLE_SSE2) && !defined(STBI_NO_SIMD) +// Note that __MINGW32__ doesn't actually mean 32-bit, so we have to avoid STBI__X64_TARGET +// +// 32-bit MinGW wants ESP to be 16-byte aligned, but this is not in the +// Windows ABI and VC++ as well as Windows DLLs don't maintain that invariant. +// As a result, enabling SSE2 on 32-bit MinGW is dangerous when not +// simultaneously enabling "-mstackrealign". +// +// See https://github.com/nothings/stb/issues/81 for more information. +// +// So default to no SSE2 on 32-bit MinGW. If you've read this far and added +// -mstackrealign to your build settings, feel free to #define STBI_MINGW_ENABLE_SSE2. +#define STBI_NO_SIMD +#endif + +#if !defined(STBI_NO_SIMD) && (defined(STBI__X86_TARGET) || defined(STBI__X64_TARGET)) +#define STBI_SSE2 +#include + +#ifdef _MSC_VER + +#if _MSC_VER >= 1400 // not VC6 +#include // __cpuid +static int stbi__cpuid3(void) +{ + int info[4]; + __cpuid(info,1); + return info[3]; +} +#else +static int stbi__cpuid3(void) +{ + int res; + __asm { + mov eax,1 + cpuid + mov res,edx + } + return res; +} +#endif + +#define STBI_SIMD_ALIGN(type, name) __declspec(align(16)) type name + +#if !defined(STBI_NO_JPEG) && defined(STBI_SSE2) +static int stbi__sse2_available(void) +{ + int info3 = stbi__cpuid3(); + return ((info3 >> 26) & 1) != 0; +} +#endif + +#else // assume GCC-style if not VC++ +#define STBI_SIMD_ALIGN(type, name) type name __attribute__((aligned(16))) + +#if !defined(STBI_NO_JPEG) && defined(STBI_SSE2) +static int stbi__sse2_available(void) +{ + // If we're even attempting to compile this on GCC/Clang, that means + // -msse2 is on, which means the compiler is allowed to use SSE2 + // instructions at will, and so are we. + return 1; +} +#endif + +#endif +#endif + +// ARM NEON +#if defined(STBI_NO_SIMD) && defined(STBI_NEON) +#undef STBI_NEON +#endif + +#ifdef STBI_NEON +#include +#ifdef _MSC_VER +#define STBI_SIMD_ALIGN(type, name) __declspec(align(16)) type name +#else +#define STBI_SIMD_ALIGN(type, name) type name __attribute__((aligned(16))) +#endif +#endif + +#ifndef STBI_SIMD_ALIGN +#define STBI_SIMD_ALIGN(type, name) type name +#endif + +#ifndef STBI_MAX_DIMENSIONS +#define STBI_MAX_DIMENSIONS (1 << 24) +#endif + +/////////////////////////////////////////////// +// +// stbi__context struct and start_xxx functions + +// stbi__context structure is our basic context used by all images, so it +// contains all the IO context, plus some basic image information +typedef struct +{ + stbi__uint32 img_x, img_y; + int img_n, img_out_n; + + stbi_io_callbacks io; + void *io_user_data; + + int read_from_callbacks; + int buflen; + stbi_uc buffer_start[128]; + int callback_already_read; + + stbi_uc *img_buffer, *img_buffer_end; + stbi_uc *img_buffer_original, *img_buffer_original_end; +} stbi__context; + + +static void stbi__refill_buffer(stbi__context *s); + +// initialize a memory-decode context +static void stbi__start_mem(stbi__context *s, stbi_uc const *buffer, int len) +{ + s->io.read = NULL; + s->read_from_callbacks = 0; + s->callback_already_read = 0; + s->img_buffer = s->img_buffer_original = (stbi_uc *) buffer; + s->img_buffer_end = s->img_buffer_original_end = (stbi_uc *) buffer+len; +} + +// initialize a callback-based context +static void stbi__start_callbacks(stbi__context *s, stbi_io_callbacks *c, void *user) +{ + s->io = *c; + s->io_user_data = user; + s->buflen = sizeof(s->buffer_start); + s->read_from_callbacks = 1; + s->callback_already_read = 0; + s->img_buffer = s->img_buffer_original = s->buffer_start; + stbi__refill_buffer(s); + s->img_buffer_original_end = s->img_buffer_end; +} + +#ifndef STBI_NO_STDIO + +static int stbi__stdio_read(void *user, char *data, int size) +{ + return (int) fread(data,1,size,(FILE*) user); +} + +static void stbi__stdio_skip(void *user, int n) +{ + int ch; + fseek((FILE*) user, n, SEEK_CUR); + ch = fgetc((FILE*) user); /* have to read a byte to reset feof()'s flag */ + if (ch != EOF) { + ungetc(ch, (FILE *) user); /* push byte back onto stream if valid. */ + } +} + +static int stbi__stdio_eof(void *user) +{ + return feof((FILE*) user) || ferror((FILE *) user); +} + +static stbi_io_callbacks stbi__stdio_callbacks = +{ + stbi__stdio_read, + stbi__stdio_skip, + stbi__stdio_eof, +}; + +static void stbi__start_file(stbi__context *s, FILE *f) +{ + stbi__start_callbacks(s, &stbi__stdio_callbacks, (void *) f); +} + +//static void stop_file(stbi__context *s) { } + +#endif // !STBI_NO_STDIO + +static void stbi__rewind(stbi__context *s) +{ + // conceptually rewind SHOULD rewind to the beginning of the stream, + // but we just rewind to the beginning of the initial buffer, because + // we only use it after doing 'test', which only ever looks at at most 92 bytes + s->img_buffer = s->img_buffer_original; + s->img_buffer_end = s->img_buffer_original_end; +} + +enum +{ + STBI_ORDER_RGB, + STBI_ORDER_BGR +}; + +typedef struct +{ + int bits_per_channel; + int num_channels; + int channel_order; +} stbi__result_info; + +#ifndef STBI_NO_JPEG +static int stbi__jpeg_test(stbi__context *s); +static void *stbi__jpeg_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri); +static int stbi__jpeg_info(stbi__context *s, int *x, int *y, int *comp); +#endif + +#ifndef STBI_NO_PNG +static int stbi__png_test(stbi__context *s); +static void *stbi__png_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri); +static int stbi__png_info(stbi__context *s, int *x, int *y, int *comp); +static int stbi__png_is16(stbi__context *s); +#endif + +#ifndef STBI_NO_BMP +static int stbi__bmp_test(stbi__context *s); +static void *stbi__bmp_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri); +static int stbi__bmp_info(stbi__context *s, int *x, int *y, int *comp); +#endif + +#ifndef STBI_NO_TGA +static int stbi__tga_test(stbi__context *s); +static void *stbi__tga_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri); +static int stbi__tga_info(stbi__context *s, int *x, int *y, int *comp); +#endif + +#ifndef STBI_NO_PSD +static int stbi__psd_test(stbi__context *s); +static void *stbi__psd_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri, int bpc); +static int stbi__psd_info(stbi__context *s, int *x, int *y, int *comp); +static int stbi__psd_is16(stbi__context *s); +#endif + +#ifndef STBI_NO_HDR +static int stbi__hdr_test(stbi__context *s); +static float *stbi__hdr_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri); +static int stbi__hdr_info(stbi__context *s, int *x, int *y, int *comp); +#endif + +#ifndef STBI_NO_PIC +static int stbi__pic_test(stbi__context *s); +static void *stbi__pic_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri); +static int stbi__pic_info(stbi__context *s, int *x, int *y, int *comp); +#endif + +#ifndef STBI_NO_GIF +static int stbi__gif_test(stbi__context *s); +static void *stbi__gif_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri); +static void *stbi__load_gif_main(stbi__context *s, int **delays, int *x, int *y, int *z, int *comp, int req_comp); +static int stbi__gif_info(stbi__context *s, int *x, int *y, int *comp); +#endif + +#ifndef STBI_NO_PNM +static int stbi__pnm_test(stbi__context *s); +static void *stbi__pnm_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri); +static int stbi__pnm_info(stbi__context *s, int *x, int *y, int *comp); +static int stbi__pnm_is16(stbi__context *s); +#endif + +static +#ifdef STBI_THREAD_LOCAL +STBI_THREAD_LOCAL +#endif +const char *stbi__g_failure_reason; + +STBIDEF const char *stbi_failure_reason(void) +{ + return stbi__g_failure_reason; +} + +#ifndef STBI_NO_FAILURE_STRINGS +static int stbi__err(const char *str) +{ + stbi__g_failure_reason = str; + return 0; +} +#endif + +static void *stbi__malloc(size_t size) +{ + return STBI_MALLOC(size); +} + +// stb_image uses ints pervasively, including for offset calculations. +// therefore the largest decoded image size we can support with the +// current code, even on 64-bit targets, is INT_MAX. this is not a +// significant limitation for the intended use case. +// +// we do, however, need to make sure our size calculations don't +// overflow. hence a few helper functions for size calculations that +// multiply integers together, making sure that they're non-negative +// and no overflow occurs. + +// return 1 if the sum is valid, 0 on overflow. +// negative terms are considered invalid. +static int stbi__addsizes_valid(int a, int b) +{ + if (b < 0) return 0; + // now 0 <= b <= INT_MAX, hence also + // 0 <= INT_MAX - b <= INTMAX. + // And "a + b <= INT_MAX" (which might overflow) is the + // same as a <= INT_MAX - b (no overflow) + return a <= INT_MAX - b; +} + +// returns 1 if the product is valid, 0 on overflow. +// negative factors are considered invalid. +static int stbi__mul2sizes_valid(int a, int b) +{ + if (a < 0 || b < 0) return 0; + if (b == 0) return 1; // mul-by-0 is always safe + // portable way to check for no overflows in a*b + return a <= INT_MAX/b; +} + +#if !defined(STBI_NO_JPEG) || !defined(STBI_NO_PNG) || !defined(STBI_NO_TGA) || !defined(STBI_NO_HDR) +// returns 1 if "a*b + add" has no negative terms/factors and doesn't overflow +static int stbi__mad2sizes_valid(int a, int b, int add) +{ + return stbi__mul2sizes_valid(a, b) && stbi__addsizes_valid(a*b, add); +} +#endif + +// returns 1 if "a*b*c + add" has no negative terms/factors and doesn't overflow +static int stbi__mad3sizes_valid(int a, int b, int c, int add) +{ + return stbi__mul2sizes_valid(a, b) && stbi__mul2sizes_valid(a*b, c) && + stbi__addsizes_valid(a*b*c, add); +} + +// returns 1 if "a*b*c*d + add" has no negative terms/factors and doesn't overflow +#if !defined(STBI_NO_LINEAR) || !defined(STBI_NO_HDR) || !defined(STBI_NO_PNM) +static int stbi__mad4sizes_valid(int a, int b, int c, int d, int add) +{ + return stbi__mul2sizes_valid(a, b) && stbi__mul2sizes_valid(a*b, c) && + stbi__mul2sizes_valid(a*b*c, d) && stbi__addsizes_valid(a*b*c*d, add); +} +#endif + +#if !defined(STBI_NO_JPEG) || !defined(STBI_NO_PNG) || !defined(STBI_NO_TGA) || !defined(STBI_NO_HDR) +// mallocs with size overflow checking +static void *stbi__malloc_mad2(int a, int b, int add) +{ + if (!stbi__mad2sizes_valid(a, b, add)) return NULL; + return stbi__malloc(a*b + add); +} +#endif + +static void *stbi__malloc_mad3(int a, int b, int c, int add) +{ + if (!stbi__mad3sizes_valid(a, b, c, add)) return NULL; + return stbi__malloc(a*b*c + add); +} + +#if !defined(STBI_NO_LINEAR) || !defined(STBI_NO_HDR) || !defined(STBI_NO_PNM) +static void *stbi__malloc_mad4(int a, int b, int c, int d, int add) +{ + if (!stbi__mad4sizes_valid(a, b, c, d, add)) return NULL; + return stbi__malloc(a*b*c*d + add); +} +#endif + +// stbi__err - error +// stbi__errpf - error returning pointer to float +// stbi__errpuc - error returning pointer to unsigned char + +#ifdef STBI_NO_FAILURE_STRINGS + #define stbi__err(x,y) 0 +#elif defined(STBI_FAILURE_USERMSG) + #define stbi__err(x,y) stbi__err(y) +#else + #define stbi__err(x,y) stbi__err(x) +#endif + +#define stbi__errpf(x,y) ((float *)(size_t) (stbi__err(x,y)?NULL:NULL)) +#define stbi__errpuc(x,y) ((unsigned char *)(size_t) (stbi__err(x,y)?NULL:NULL)) + +STBIDEF void stbi_image_free(void *retval_from_stbi_load) +{ + STBI_FREE(retval_from_stbi_load); +} + +#ifndef STBI_NO_LINEAR +static float *stbi__ldr_to_hdr(stbi_uc *data, int x, int y, int comp); +#endif + +#ifndef STBI_NO_HDR +static stbi_uc *stbi__hdr_to_ldr(float *data, int x, int y, int comp); +#endif + +static int stbi__vertically_flip_on_load_global = 0; + +STBIDEF void stbi_set_flip_vertically_on_load(int flag_true_if_should_flip) +{ + stbi__vertically_flip_on_load_global = flag_true_if_should_flip; +} + +#ifndef STBI_THREAD_LOCAL +#define stbi__vertically_flip_on_load stbi__vertically_flip_on_load_global +#else +static STBI_THREAD_LOCAL int stbi__vertically_flip_on_load_local, stbi__vertically_flip_on_load_set; + +STBIDEF void stbi_set_flip_vertically_on_load_thread(int flag_true_if_should_flip) +{ + stbi__vertically_flip_on_load_local = flag_true_if_should_flip; + stbi__vertically_flip_on_load_set = 1; +} + +#define stbi__vertically_flip_on_load (stbi__vertically_flip_on_load_set \ + ? stbi__vertically_flip_on_load_local \ + : stbi__vertically_flip_on_load_global) +#endif // STBI_THREAD_LOCAL + +static void *stbi__load_main(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri, int bpc) +{ + memset(ri, 0, sizeof(*ri)); // make sure it's initialized if we add new fields + ri->bits_per_channel = 8; // default is 8 so most paths don't have to be changed + ri->channel_order = STBI_ORDER_RGB; // all current input & output are this, but this is here so we can add BGR order + ri->num_channels = 0; + + // test the formats with a very explicit header first (at least a FOURCC + // or distinctive magic number first) + #ifndef STBI_NO_PNG + if (stbi__png_test(s)) return stbi__png_load(s,x,y,comp,req_comp, ri); + #endif + #ifndef STBI_NO_BMP + if (stbi__bmp_test(s)) return stbi__bmp_load(s,x,y,comp,req_comp, ri); + #endif + #ifndef STBI_NO_GIF + if (stbi__gif_test(s)) return stbi__gif_load(s,x,y,comp,req_comp, ri); + #endif + #ifndef STBI_NO_PSD + if (stbi__psd_test(s)) return stbi__psd_load(s,x,y,comp,req_comp, ri, bpc); + #else + STBI_NOTUSED(bpc); + #endif + #ifndef STBI_NO_PIC + if (stbi__pic_test(s)) return stbi__pic_load(s,x,y,comp,req_comp, ri); + #endif + + // then the formats that can end up attempting to load with just 1 or 2 + // bytes matching expectations; these are prone to false positives, so + // try them later + #ifndef STBI_NO_JPEG + if (stbi__jpeg_test(s)) return stbi__jpeg_load(s,x,y,comp,req_comp, ri); + #endif + #ifndef STBI_NO_PNM + if (stbi__pnm_test(s)) return stbi__pnm_load(s,x,y,comp,req_comp, ri); + #endif + + #ifndef STBI_NO_HDR + if (stbi__hdr_test(s)) { + float *hdr = stbi__hdr_load(s, x,y,comp,req_comp, ri); + return stbi__hdr_to_ldr(hdr, *x, *y, req_comp ? req_comp : *comp); + } + #endif + + #ifndef STBI_NO_TGA + // test tga last because it's a crappy test! + if (stbi__tga_test(s)) + return stbi__tga_load(s,x,y,comp,req_comp, ri); + #endif + + return stbi__errpuc("unknown image type", "Image not of any known type, or corrupt"); +} + +static stbi_uc *stbi__convert_16_to_8(stbi__uint16 *orig, int w, int h, int channels) +{ + int i; + int img_len = w * h * channels; + stbi_uc *reduced; + + reduced = (stbi_uc *) stbi__malloc(img_len); + if (reduced == NULL) return stbi__errpuc("outofmem", "Out of memory"); + + for (i = 0; i < img_len; ++i) + reduced[i] = (stbi_uc)((orig[i] >> 8) & 0xFF); // top half of each byte is sufficient approx of 16->8 bit scaling + + STBI_FREE(orig); + return reduced; +} + +static stbi__uint16 *stbi__convert_8_to_16(stbi_uc *orig, int w, int h, int channels) +{ + int i; + int img_len = w * h * channels; + stbi__uint16 *enlarged; + + enlarged = (stbi__uint16 *) stbi__malloc(img_len*2); + if (enlarged == NULL) return (stbi__uint16 *) stbi__errpuc("outofmem", "Out of memory"); + + for (i = 0; i < img_len; ++i) + enlarged[i] = (stbi__uint16)((orig[i] << 8) + orig[i]); // replicate to high and low byte, maps 0->0, 255->0xffff + + STBI_FREE(orig); + return enlarged; +} + +static void stbi__vertical_flip(void *image, int w, int h, int bytes_per_pixel) +{ + int row; + size_t bytes_per_row = (size_t)w * bytes_per_pixel; + stbi_uc temp[2048]; + stbi_uc *bytes = (stbi_uc *)image; + + for (row = 0; row < (h>>1); row++) { + stbi_uc *row0 = bytes + row*bytes_per_row; + stbi_uc *row1 = bytes + (h - row - 1)*bytes_per_row; + // swap row0 with row1 + size_t bytes_left = bytes_per_row; + while (bytes_left) { + size_t bytes_copy = (bytes_left < sizeof(temp)) ? bytes_left : sizeof(temp); + memcpy(temp, row0, bytes_copy); + memcpy(row0, row1, bytes_copy); + memcpy(row1, temp, bytes_copy); + row0 += bytes_copy; + row1 += bytes_copy; + bytes_left -= bytes_copy; + } + } +} + +#ifndef STBI_NO_GIF +static void stbi__vertical_flip_slices(void *image, int w, int h, int z, int bytes_per_pixel) +{ + int slice; + int slice_size = w * h * bytes_per_pixel; + + stbi_uc *bytes = (stbi_uc *)image; + for (slice = 0; slice < z; ++slice) { + stbi__vertical_flip(bytes, w, h, bytes_per_pixel); + bytes += slice_size; + } +} +#endif + +static unsigned char *stbi__load_and_postprocess_8bit(stbi__context *s, int *x, int *y, int *comp, int req_comp) +{ + stbi__result_info ri; + void *result = stbi__load_main(s, x, y, comp, req_comp, &ri, 8); + + if (result == NULL) + return NULL; + + // it is the responsibility of the loaders to make sure we get either 8 or 16 bit. + STBI_ASSERT(ri.bits_per_channel == 8 || ri.bits_per_channel == 16); + + if (ri.bits_per_channel != 8) { + result = stbi__convert_16_to_8((stbi__uint16 *) result, *x, *y, req_comp == 0 ? *comp : req_comp); + ri.bits_per_channel = 8; + } + + // @TODO: move stbi__convert_format to here + + if (stbi__vertically_flip_on_load) { + int channels = req_comp ? req_comp : *comp; + stbi__vertical_flip(result, *x, *y, channels * sizeof(stbi_uc)); + } + + return (unsigned char *) result; +} + +static stbi__uint16 *stbi__load_and_postprocess_16bit(stbi__context *s, int *x, int *y, int *comp, int req_comp) +{ + stbi__result_info ri; + void *result = stbi__load_main(s, x, y, comp, req_comp, &ri, 16); + + if (result == NULL) + return NULL; + + // it is the responsibility of the loaders to make sure we get either 8 or 16 bit. + STBI_ASSERT(ri.bits_per_channel == 8 || ri.bits_per_channel == 16); + + if (ri.bits_per_channel != 16) { + result = stbi__convert_8_to_16((stbi_uc *) result, *x, *y, req_comp == 0 ? *comp : req_comp); + ri.bits_per_channel = 16; + } + + // @TODO: move stbi__convert_format16 to here + // @TODO: special case RGB-to-Y (and RGBA-to-YA) for 8-bit-to-16-bit case to keep more precision + + if (stbi__vertically_flip_on_load) { + int channels = req_comp ? req_comp : *comp; + stbi__vertical_flip(result, *x, *y, channels * sizeof(stbi__uint16)); + } + + return (stbi__uint16 *) result; +} + +#if !defined(STBI_NO_HDR) && !defined(STBI_NO_LINEAR) +static void stbi__float_postprocess(float *result, int *x, int *y, int *comp, int req_comp) +{ + if (stbi__vertically_flip_on_load && result != NULL) { + int channels = req_comp ? req_comp : *comp; + stbi__vertical_flip(result, *x, *y, channels * sizeof(float)); + } +} +#endif + +#ifndef STBI_NO_STDIO + +#if defined(_WIN32) && defined(STBI_WINDOWS_UTF8) +STBI_EXTERN __declspec(dllimport) int __stdcall MultiByteToWideChar(unsigned int cp, unsigned long flags, const char *str, int cbmb, wchar_t *widestr, int cchwide); +STBI_EXTERN __declspec(dllimport) int __stdcall WideCharToMultiByte(unsigned int cp, unsigned long flags, const wchar_t *widestr, int cchwide, char *str, int cbmb, const char *defchar, int *used_default); +#endif + +#if defined(_WIN32) && defined(STBI_WINDOWS_UTF8) +STBIDEF int stbi_convert_wchar_to_utf8(char *buffer, size_t bufferlen, const wchar_t* input) +{ + return WideCharToMultiByte(65001 /* UTF8 */, 0, input, -1, buffer, (int) bufferlen, NULL, NULL); +} +#endif + +static FILE *stbi__fopen(char const *filename, char const *mode) +{ + FILE *f; +#if defined(_WIN32) && defined(STBI_WINDOWS_UTF8) + wchar_t wMode[64]; + wchar_t wFilename[1024]; + if (0 == MultiByteToWideChar(65001 /* UTF8 */, 0, filename, -1, wFilename, sizeof(wFilename)/sizeof(*wFilename))) + return 0; + + if (0 == MultiByteToWideChar(65001 /* UTF8 */, 0, mode, -1, wMode, sizeof(wMode)/sizeof(*wMode))) + return 0; + +#if defined(_MSC_VER) && _MSC_VER >= 1400 + if (0 != _wfopen_s(&f, wFilename, wMode)) + f = 0; +#else + f = _wfopen(wFilename, wMode); +#endif + +#elif defined(_MSC_VER) && _MSC_VER >= 1400 + if (0 != fopen_s(&f, filename, mode)) + f=0; +#else + f = fopen(filename, mode); +#endif + return f; +} + + +STBIDEF stbi_uc *stbi_load(char const *filename, int *x, int *y, int *comp, int req_comp) +{ + FILE *f = stbi__fopen(filename, "rb"); + unsigned char *result; + if (!f) return stbi__errpuc("can't fopen", "Unable to open file"); + result = stbi_load_from_file(f,x,y,comp,req_comp); + fclose(f); + return result; +} + +STBIDEF stbi_uc *stbi_load_from_file(FILE *f, int *x, int *y, int *comp, int req_comp) +{ + unsigned char *result; + stbi__context s; + stbi__start_file(&s,f); + result = stbi__load_and_postprocess_8bit(&s,x,y,comp,req_comp); + if (result) { + // need to 'unget' all the characters in the IO buffer + fseek(f, - (int) (s.img_buffer_end - s.img_buffer), SEEK_CUR); + } + return result; +} + +STBIDEF stbi__uint16 *stbi_load_from_file_16(FILE *f, int *x, int *y, int *comp, int req_comp) +{ + stbi__uint16 *result; + stbi__context s; + stbi__start_file(&s,f); + result = stbi__load_and_postprocess_16bit(&s,x,y,comp,req_comp); + if (result) { + // need to 'unget' all the characters in the IO buffer + fseek(f, - (int) (s.img_buffer_end - s.img_buffer), SEEK_CUR); + } + return result; +} + +STBIDEF stbi_us *stbi_load_16(char const *filename, int *x, int *y, int *comp, int req_comp) +{ + FILE *f = stbi__fopen(filename, "rb"); + stbi__uint16 *result; + if (!f) return (stbi_us *) stbi__errpuc("can't fopen", "Unable to open file"); + result = stbi_load_from_file_16(f,x,y,comp,req_comp); + fclose(f); + return result; +} + + +#endif //!STBI_NO_STDIO + +STBIDEF stbi_us *stbi_load_16_from_memory(stbi_uc const *buffer, int len, int *x, int *y, int *channels_in_file, int desired_channels) +{ + stbi__context s; + stbi__start_mem(&s,buffer,len); + return stbi__load_and_postprocess_16bit(&s,x,y,channels_in_file,desired_channels); +} + +STBIDEF stbi_us *stbi_load_16_from_callbacks(stbi_io_callbacks const *clbk, void *user, int *x, int *y, int *channels_in_file, int desired_channels) +{ + stbi__context s; + stbi__start_callbacks(&s, (stbi_io_callbacks *)clbk, user); + return stbi__load_and_postprocess_16bit(&s,x,y,channels_in_file,desired_channels); +} + +STBIDEF stbi_uc *stbi_load_from_memory(stbi_uc const *buffer, int len, int *x, int *y, int *comp, int req_comp) +{ + stbi__context s; + stbi__start_mem(&s,buffer,len); + return stbi__load_and_postprocess_8bit(&s,x,y,comp,req_comp); +} + +STBIDEF stbi_uc *stbi_load_from_callbacks(stbi_io_callbacks const *clbk, void *user, int *x, int *y, int *comp, int req_comp) +{ + stbi__context s; + stbi__start_callbacks(&s, (stbi_io_callbacks *) clbk, user); + return stbi__load_and_postprocess_8bit(&s,x,y,comp,req_comp); +} + +#ifndef STBI_NO_GIF +STBIDEF stbi_uc *stbi_load_gif_from_memory(stbi_uc const *buffer, int len, int **delays, int *x, int *y, int *z, int *comp, int req_comp) +{ + unsigned char *result; + stbi__context s; + stbi__start_mem(&s,buffer,len); + + result = (unsigned char*) stbi__load_gif_main(&s, delays, x, y, z, comp, req_comp); + if (stbi__vertically_flip_on_load) { + stbi__vertical_flip_slices( result, *x, *y, *z, *comp ); + } + + return result; +} +#endif + +#ifndef STBI_NO_LINEAR +static float *stbi__loadf_main(stbi__context *s, int *x, int *y, int *comp, int req_comp) +{ + unsigned char *data; + #ifndef STBI_NO_HDR + if (stbi__hdr_test(s)) { + stbi__result_info ri; + float *hdr_data = stbi__hdr_load(s,x,y,comp,req_comp, &ri); + if (hdr_data) + stbi__float_postprocess(hdr_data,x,y,comp,req_comp); + return hdr_data; + } + #endif + data = stbi__load_and_postprocess_8bit(s, x, y, comp, req_comp); + if (data) + return stbi__ldr_to_hdr(data, *x, *y, req_comp ? req_comp : *comp); + return stbi__errpf("unknown image type", "Image not of any known type, or corrupt"); +} + +STBIDEF float *stbi_loadf_from_memory(stbi_uc const *buffer, int len, int *x, int *y, int *comp, int req_comp) +{ + stbi__context s; + stbi__start_mem(&s,buffer,len); + return stbi__loadf_main(&s,x,y,comp,req_comp); +} + +STBIDEF float *stbi_loadf_from_callbacks(stbi_io_callbacks const *clbk, void *user, int *x, int *y, int *comp, int req_comp) +{ + stbi__context s; + stbi__start_callbacks(&s, (stbi_io_callbacks *) clbk, user); + return stbi__loadf_main(&s,x,y,comp,req_comp); +} + +#ifndef STBI_NO_STDIO +STBIDEF float *stbi_loadf(char const *filename, int *x, int *y, int *comp, int req_comp) +{ + float *result; + FILE *f = stbi__fopen(filename, "rb"); + if (!f) return stbi__errpf("can't fopen", "Unable to open file"); + result = stbi_loadf_from_file(f,x,y,comp,req_comp); + fclose(f); + return result; +} + +STBIDEF float *stbi_loadf_from_file(FILE *f, int *x, int *y, int *comp, int req_comp) +{ + stbi__context s; + stbi__start_file(&s,f); + return stbi__loadf_main(&s,x,y,comp,req_comp); +} +#endif // !STBI_NO_STDIO + +#endif // !STBI_NO_LINEAR + +// these is-hdr-or-not is defined independent of whether STBI_NO_LINEAR is +// defined, for API simplicity; if STBI_NO_LINEAR is defined, it always +// reports false! + +STBIDEF int stbi_is_hdr_from_memory(stbi_uc const *buffer, int len) +{ + #ifndef STBI_NO_HDR + stbi__context s; + stbi__start_mem(&s,buffer,len); + return stbi__hdr_test(&s); + #else + STBI_NOTUSED(buffer); + STBI_NOTUSED(len); + return 0; + #endif +} + +#ifndef STBI_NO_STDIO +STBIDEF int stbi_is_hdr (char const *filename) +{ + FILE *f = stbi__fopen(filename, "rb"); + int result=0; + if (f) { + result = stbi_is_hdr_from_file(f); + fclose(f); + } + return result; +} + +STBIDEF int stbi_is_hdr_from_file(FILE *f) +{ + #ifndef STBI_NO_HDR + long pos = ftell(f); + int res; + stbi__context s; + stbi__start_file(&s,f); + res = stbi__hdr_test(&s); + fseek(f, pos, SEEK_SET); + return res; + #else + STBI_NOTUSED(f); + return 0; + #endif +} +#endif // !STBI_NO_STDIO + +STBIDEF int stbi_is_hdr_from_callbacks(stbi_io_callbacks const *clbk, void *user) +{ + #ifndef STBI_NO_HDR + stbi__context s; + stbi__start_callbacks(&s, (stbi_io_callbacks *) clbk, user); + return stbi__hdr_test(&s); + #else + STBI_NOTUSED(clbk); + STBI_NOTUSED(user); + return 0; + #endif +} + +#ifndef STBI_NO_LINEAR +static float stbi__l2h_gamma=2.2f, stbi__l2h_scale=1.0f; + +STBIDEF void stbi_ldr_to_hdr_gamma(float gamma) { stbi__l2h_gamma = gamma; } +STBIDEF void stbi_ldr_to_hdr_scale(float scale) { stbi__l2h_scale = scale; } +#endif + +static float stbi__h2l_gamma_i=1.0f/2.2f, stbi__h2l_scale_i=1.0f; + +STBIDEF void stbi_hdr_to_ldr_gamma(float gamma) { stbi__h2l_gamma_i = 1/gamma; } +STBIDEF void stbi_hdr_to_ldr_scale(float scale) { stbi__h2l_scale_i = 1/scale; } + + +////////////////////////////////////////////////////////////////////////////// +// +// Common code used by all image loaders +// + +enum +{ + STBI__SCAN_load=0, + STBI__SCAN_type, + STBI__SCAN_header +}; + +static void stbi__refill_buffer(stbi__context *s) +{ + int n = (s->io.read)(s->io_user_data,(char*)s->buffer_start,s->buflen); + s->callback_already_read += (int) (s->img_buffer - s->img_buffer_original); + if (n == 0) { + // at end of file, treat same as if from memory, but need to handle case + // where s->img_buffer isn't pointing to safe memory, e.g. 0-byte file + s->read_from_callbacks = 0; + s->img_buffer = s->buffer_start; + s->img_buffer_end = s->buffer_start+1; + *s->img_buffer = 0; + } else { + s->img_buffer = s->buffer_start; + s->img_buffer_end = s->buffer_start + n; + } +} + +stbi_inline static stbi_uc stbi__get8(stbi__context *s) +{ + if (s->img_buffer < s->img_buffer_end) + return *s->img_buffer++; + if (s->read_from_callbacks) { + stbi__refill_buffer(s); + return *s->img_buffer++; + } + return 0; +} + +#if defined(STBI_NO_JPEG) && defined(STBI_NO_HDR) && defined(STBI_NO_PIC) && defined(STBI_NO_PNM) +// nothing +#else +stbi_inline static int stbi__at_eof(stbi__context *s) +{ + if (s->io.read) { + if (!(s->io.eof)(s->io_user_data)) return 0; + // if feof() is true, check if buffer = end + // special case: we've only got the special 0 character at the end + if (s->read_from_callbacks == 0) return 1; + } + + return s->img_buffer >= s->img_buffer_end; +} +#endif + +#if defined(STBI_NO_JPEG) && defined(STBI_NO_PNG) && defined(STBI_NO_BMP) && defined(STBI_NO_PSD) && defined(STBI_NO_TGA) && defined(STBI_NO_GIF) && defined(STBI_NO_PIC) +// nothing +#else +static void stbi__skip(stbi__context *s, int n) +{ + if (n == 0) return; // already there! + if (n < 0) { + s->img_buffer = s->img_buffer_end; + return; + } + if (s->io.read) { + int blen = (int) (s->img_buffer_end - s->img_buffer); + if (blen < n) { + s->img_buffer = s->img_buffer_end; + (s->io.skip)(s->io_user_data, n - blen); + return; + } + } + s->img_buffer += n; +} +#endif + +#if defined(STBI_NO_PNG) && defined(STBI_NO_TGA) && defined(STBI_NO_HDR) && defined(STBI_NO_PNM) +// nothing +#else +static int stbi__getn(stbi__context *s, stbi_uc *buffer, int n) +{ + if (s->io.read) { + int blen = (int) (s->img_buffer_end - s->img_buffer); + if (blen < n) { + int res, count; + + memcpy(buffer, s->img_buffer, blen); + + count = (s->io.read)(s->io_user_data, (char*) buffer + blen, n - blen); + res = (count == (n-blen)); + s->img_buffer = s->img_buffer_end; + return res; + } + } + + if (s->img_buffer+n <= s->img_buffer_end) { + memcpy(buffer, s->img_buffer, n); + s->img_buffer += n; + return 1; + } else + return 0; +} +#endif + +#if defined(STBI_NO_JPEG) && defined(STBI_NO_PNG) && defined(STBI_NO_PSD) && defined(STBI_NO_PIC) +// nothing +#else +static int stbi__get16be(stbi__context *s) +{ + int z = stbi__get8(s); + return (z << 8) + stbi__get8(s); +} +#endif + +#if defined(STBI_NO_PNG) && defined(STBI_NO_PSD) && defined(STBI_NO_PIC) +// nothing +#else +static stbi__uint32 stbi__get32be(stbi__context *s) +{ + stbi__uint32 z = stbi__get16be(s); + return (z << 16) + stbi__get16be(s); +} +#endif + +#if defined(STBI_NO_BMP) && defined(STBI_NO_TGA) && defined(STBI_NO_GIF) +// nothing +#else +static int stbi__get16le(stbi__context *s) +{ + int z = stbi__get8(s); + return z + (stbi__get8(s) << 8); +} +#endif + +#ifndef STBI_NO_BMP +static stbi__uint32 stbi__get32le(stbi__context *s) +{ + stbi__uint32 z = stbi__get16le(s); + z += (stbi__uint32)stbi__get16le(s) << 16; + return z; +} +#endif + +#define STBI__BYTECAST(x) ((stbi_uc) ((x) & 255)) // truncate int to byte without warnings + +#if defined(STBI_NO_JPEG) && defined(STBI_NO_PNG) && defined(STBI_NO_BMP) && defined(STBI_NO_PSD) && defined(STBI_NO_TGA) && defined(STBI_NO_GIF) && defined(STBI_NO_PIC) && defined(STBI_NO_PNM) +// nothing +#else +////////////////////////////////////////////////////////////////////////////// +// +// generic converter from built-in img_n to req_comp +// individual types do this automatically as much as possible (e.g. jpeg +// does all cases internally since it needs to colorspace convert anyway, +// and it never has alpha, so very few cases ). png can automatically +// interleave an alpha=255 channel, but falls back to this for other cases +// +// assume data buffer is malloced, so malloc a new one and free that one +// only failure mode is malloc failing + +static stbi_uc stbi__compute_y(int r, int g, int b) +{ + return (stbi_uc) (((r*77) + (g*150) + (29*b)) >> 8); +} +#endif + +#if defined(STBI_NO_PNG) && defined(STBI_NO_BMP) && defined(STBI_NO_PSD) && defined(STBI_NO_TGA) && defined(STBI_NO_GIF) && defined(STBI_NO_PIC) && defined(STBI_NO_PNM) +// nothing +#else +static unsigned char *stbi__convert_format(unsigned char *data, int img_n, int req_comp, unsigned int x, unsigned int y) +{ + int i,j; + unsigned char *good; + + if (req_comp == img_n) return data; + STBI_ASSERT(req_comp >= 1 && req_comp <= 4); + + good = (unsigned char *) stbi__malloc_mad3(req_comp, x, y, 0); + if (good == NULL) { + STBI_FREE(data); + return stbi__errpuc("outofmem", "Out of memory"); + } + + for (j=0; j < (int) y; ++j) { + unsigned char *src = data + j * x * img_n ; + unsigned char *dest = good + j * x * req_comp; + + #define STBI__COMBO(a,b) ((a)*8+(b)) + #define STBI__CASE(a,b) case STBI__COMBO(a,b): for(i=x-1; i >= 0; --i, src += a, dest += b) + // convert source image with img_n components to one with req_comp components; + // avoid switch per pixel, so use switch per scanline and massive macros + switch (STBI__COMBO(img_n, req_comp)) { + STBI__CASE(1,2) { dest[0]=src[0]; dest[1]=255; } break; + STBI__CASE(1,3) { dest[0]=dest[1]=dest[2]=src[0]; } break; + STBI__CASE(1,4) { dest[0]=dest[1]=dest[2]=src[0]; dest[3]=255; } break; + STBI__CASE(2,1) { dest[0]=src[0]; } break; + STBI__CASE(2,3) { dest[0]=dest[1]=dest[2]=src[0]; } break; + STBI__CASE(2,4) { dest[0]=dest[1]=dest[2]=src[0]; dest[3]=src[1]; } break; + STBI__CASE(3,4) { dest[0]=src[0];dest[1]=src[1];dest[2]=src[2];dest[3]=255; } break; + STBI__CASE(3,1) { dest[0]=stbi__compute_y(src[0],src[1],src[2]); } break; + STBI__CASE(3,2) { dest[0]=stbi__compute_y(src[0],src[1],src[2]); dest[1] = 255; } break; + STBI__CASE(4,1) { dest[0]=stbi__compute_y(src[0],src[1],src[2]); } break; + STBI__CASE(4,2) { dest[0]=stbi__compute_y(src[0],src[1],src[2]); dest[1] = src[3]; } break; + STBI__CASE(4,3) { dest[0]=src[0];dest[1]=src[1];dest[2]=src[2]; } break; + default: STBI_ASSERT(0); STBI_FREE(data); STBI_FREE(good); return stbi__errpuc("unsupported", "Unsupported format conversion"); + } + #undef STBI__CASE + } + + STBI_FREE(data); + return good; +} +#endif + +#if defined(STBI_NO_PNG) && defined(STBI_NO_PSD) +// nothing +#else +static stbi__uint16 stbi__compute_y_16(int r, int g, int b) +{ + return (stbi__uint16) (((r*77) + (g*150) + (29*b)) >> 8); +} +#endif + +#if defined(STBI_NO_PNG) && defined(STBI_NO_PSD) +// nothing +#else +static stbi__uint16 *stbi__convert_format16(stbi__uint16 *data, int img_n, int req_comp, unsigned int x, unsigned int y) +{ + int i,j; + stbi__uint16 *good; + + if (req_comp == img_n) return data; + STBI_ASSERT(req_comp >= 1 && req_comp <= 4); + + good = (stbi__uint16 *) stbi__malloc(req_comp * x * y * 2); + if (good == NULL) { + STBI_FREE(data); + return (stbi__uint16 *) stbi__errpuc("outofmem", "Out of memory"); + } + + for (j=0; j < (int) y; ++j) { + stbi__uint16 *src = data + j * x * img_n ; + stbi__uint16 *dest = good + j * x * req_comp; + + #define STBI__COMBO(a,b) ((a)*8+(b)) + #define STBI__CASE(a,b) case STBI__COMBO(a,b): for(i=x-1; i >= 0; --i, src += a, dest += b) + // convert source image with img_n components to one with req_comp components; + // avoid switch per pixel, so use switch per scanline and massive macros + switch (STBI__COMBO(img_n, req_comp)) { + STBI__CASE(1,2) { dest[0]=src[0]; dest[1]=0xffff; } break; + STBI__CASE(1,3) { dest[0]=dest[1]=dest[2]=src[0]; } break; + STBI__CASE(1,4) { dest[0]=dest[1]=dest[2]=src[0]; dest[3]=0xffff; } break; + STBI__CASE(2,1) { dest[0]=src[0]; } break; + STBI__CASE(2,3) { dest[0]=dest[1]=dest[2]=src[0]; } break; + STBI__CASE(2,4) { dest[0]=dest[1]=dest[2]=src[0]; dest[3]=src[1]; } break; + STBI__CASE(3,4) { dest[0]=src[0];dest[1]=src[1];dest[2]=src[2];dest[3]=0xffff; } break; + STBI__CASE(3,1) { dest[0]=stbi__compute_y_16(src[0],src[1],src[2]); } break; + STBI__CASE(3,2) { dest[0]=stbi__compute_y_16(src[0],src[1],src[2]); dest[1] = 0xffff; } break; + STBI__CASE(4,1) { dest[0]=stbi__compute_y_16(src[0],src[1],src[2]); } break; + STBI__CASE(4,2) { dest[0]=stbi__compute_y_16(src[0],src[1],src[2]); dest[1] = src[3]; } break; + STBI__CASE(4,3) { dest[0]=src[0];dest[1]=src[1];dest[2]=src[2]; } break; + default: STBI_ASSERT(0); STBI_FREE(data); STBI_FREE(good); return (stbi__uint16*) stbi__errpuc("unsupported", "Unsupported format conversion"); + } + #undef STBI__CASE + } + + STBI_FREE(data); + return good; +} +#endif + +#ifndef STBI_NO_LINEAR +static float *stbi__ldr_to_hdr(stbi_uc *data, int x, int y, int comp) +{ + int i,k,n; + float *output; + if (!data) return NULL; + output = (float *) stbi__malloc_mad4(x, y, comp, sizeof(float), 0); + if (output == NULL) { STBI_FREE(data); return stbi__errpf("outofmem", "Out of memory"); } + // compute number of non-alpha components + if (comp & 1) n = comp; else n = comp-1; + for (i=0; i < x*y; ++i) { + for (k=0; k < n; ++k) { + output[i*comp + k] = (float) (pow(data[i*comp+k]/255.0f, stbi__l2h_gamma) * stbi__l2h_scale); + } + } + if (n < comp) { + for (i=0; i < x*y; ++i) { + output[i*comp + n] = data[i*comp + n]/255.0f; + } + } + STBI_FREE(data); + return output; +} +#endif + +#ifndef STBI_NO_HDR +#define stbi__float2int(x) ((int) (x)) +static stbi_uc *stbi__hdr_to_ldr(float *data, int x, int y, int comp) +{ + int i,k,n; + stbi_uc *output; + if (!data) return NULL; + output = (stbi_uc *) stbi__malloc_mad3(x, y, comp, 0); + if (output == NULL) { STBI_FREE(data); return stbi__errpuc("outofmem", "Out of memory"); } + // compute number of non-alpha components + if (comp & 1) n = comp; else n = comp-1; + for (i=0; i < x*y; ++i) { + for (k=0; k < n; ++k) { + float z = (float) pow(data[i*comp+k]*stbi__h2l_scale_i, stbi__h2l_gamma_i) * 255 + 0.5f; + if (z < 0) z = 0; + if (z > 255) z = 255; + output[i*comp + k] = (stbi_uc) stbi__float2int(z); + } + if (k < comp) { + float z = data[i*comp+k] * 255 + 0.5f; + if (z < 0) z = 0; + if (z > 255) z = 255; + output[i*comp + k] = (stbi_uc) stbi__float2int(z); + } + } + STBI_FREE(data); + return output; +} +#endif + +////////////////////////////////////////////////////////////////////////////// +// +// "baseline" JPEG/JFIF decoder +// +// simple implementation +// - doesn't support delayed output of y-dimension +// - simple interface (only one output format: 8-bit interleaved RGB) +// - doesn't try to recover corrupt jpegs +// - doesn't allow partial loading, loading multiple at once +// - still fast on x86 (copying globals into locals doesn't help x86) +// - allocates lots of intermediate memory (full size of all components) +// - non-interleaved case requires this anyway +// - allows good upsampling (see next) +// high-quality +// - upsampled channels are bilinearly interpolated, even across blocks +// - quality integer IDCT derived from IJG's 'slow' +// performance +// - fast huffman; reasonable integer IDCT +// - some SIMD kernels for common paths on targets with SSE2/NEON +// - uses a lot of intermediate memory, could cache poorly + +#ifndef STBI_NO_JPEG + +// huffman decoding acceleration +#define FAST_BITS 9 // larger handles more cases; smaller stomps less cache + +typedef struct +{ + stbi_uc fast[1 << FAST_BITS]; + // weirdly, repacking this into AoS is a 10% speed loss, instead of a win + stbi__uint16 code[256]; + stbi_uc values[256]; + stbi_uc size[257]; + unsigned int maxcode[18]; + int delta[17]; // old 'firstsymbol' - old 'firstcode' +} stbi__huffman; + +typedef struct +{ + stbi__context *s; + stbi__huffman huff_dc[4]; + stbi__huffman huff_ac[4]; + stbi__uint16 dequant[4][64]; + stbi__int16 fast_ac[4][1 << FAST_BITS]; + +// sizes for components, interleaved MCUs + int img_h_max, img_v_max; + int img_mcu_x, img_mcu_y; + int img_mcu_w, img_mcu_h; + +// definition of jpeg image component + struct + { + int id; + int h,v; + int tq; + int hd,ha; + int dc_pred; + + int x,y,w2,h2; + stbi_uc *data; + void *raw_data, *raw_coeff; + stbi_uc *linebuf; + short *coeff; // progressive only + int coeff_w, coeff_h; // number of 8x8 coefficient blocks + } img_comp[4]; + + stbi__uint32 code_buffer; // jpeg entropy-coded buffer + int code_bits; // number of valid bits + unsigned char marker; // marker seen while filling entropy buffer + int nomore; // flag if we saw a marker so must stop + + int progressive; + int spec_start; + int spec_end; + int succ_high; + int succ_low; + int eob_run; + int jfif; + int app14_color_transform; // Adobe APP14 tag + int rgb; + + int scan_n, order[4]; + int restart_interval, todo; + +// kernels + void (*idct_block_kernel)(stbi_uc *out, int out_stride, short data[64]); + void (*YCbCr_to_RGB_kernel)(stbi_uc *out, const stbi_uc *y, const stbi_uc *pcb, const stbi_uc *pcr, int count, int step); + stbi_uc *(*resample_row_hv_2_kernel)(stbi_uc *out, stbi_uc *in_near, stbi_uc *in_far, int w, int hs); +} stbi__jpeg; + +static int stbi__build_huffman(stbi__huffman *h, int *count) +{ + int i,j,k=0; + unsigned int code; + // build size list for each symbol (from JPEG spec) + for (i=0; i < 16; ++i) + for (j=0; j < count[i]; ++j) + h->size[k++] = (stbi_uc) (i+1); + h->size[k] = 0; + + // compute actual symbols (from jpeg spec) + code = 0; + k = 0; + for(j=1; j <= 16; ++j) { + // compute delta to add to code to compute symbol id + h->delta[j] = k - code; + if (h->size[k] == j) { + while (h->size[k] == j) + h->code[k++] = (stbi__uint16) (code++); + if (code-1 >= (1u << j)) return stbi__err("bad code lengths","Corrupt JPEG"); + } + // compute largest code + 1 for this size, preshifted as needed later + h->maxcode[j] = code << (16-j); + code <<= 1; + } + h->maxcode[j] = 0xffffffff; + + // build non-spec acceleration table; 255 is flag for not-accelerated + memset(h->fast, 255, 1 << FAST_BITS); + for (i=0; i < k; ++i) { + int s = h->size[i]; + if (s <= FAST_BITS) { + int c = h->code[i] << (FAST_BITS-s); + int m = 1 << (FAST_BITS-s); + for (j=0; j < m; ++j) { + h->fast[c+j] = (stbi_uc) i; + } + } + } + return 1; +} + +// build a table that decodes both magnitude and value of small ACs in +// one go. +static void stbi__build_fast_ac(stbi__int16 *fast_ac, stbi__huffman *h) +{ + int i; + for (i=0; i < (1 << FAST_BITS); ++i) { + stbi_uc fast = h->fast[i]; + fast_ac[i] = 0; + if (fast < 255) { + int rs = h->values[fast]; + int run = (rs >> 4) & 15; + int magbits = rs & 15; + int len = h->size[fast]; + + if (magbits && len + magbits <= FAST_BITS) { + // magnitude code followed by receive_extend code + int k = ((i << len) & ((1 << FAST_BITS) - 1)) >> (FAST_BITS - magbits); + int m = 1 << (magbits - 1); + if (k < m) k += (~0U << magbits) + 1; + // if the result is small enough, we can fit it in fast_ac table + if (k >= -128 && k <= 127) + fast_ac[i] = (stbi__int16) ((k * 256) + (run * 16) + (len + magbits)); + } + } + } +} + +static void stbi__grow_buffer_unsafe(stbi__jpeg *j) +{ + do { + unsigned int b = j->nomore ? 0 : stbi__get8(j->s); + if (b == 0xff) { + int c = stbi__get8(j->s); + while (c == 0xff) c = stbi__get8(j->s); // consume fill bytes + if (c != 0) { + j->marker = (unsigned char) c; + j->nomore = 1; + return; + } + } + j->code_buffer |= b << (24 - j->code_bits); + j->code_bits += 8; + } while (j->code_bits <= 24); +} + +// (1 << n) - 1 +static const stbi__uint32 stbi__bmask[17]={0,1,3,7,15,31,63,127,255,511,1023,2047,4095,8191,16383,32767,65535}; + +// decode a jpeg huffman value from the bitstream +stbi_inline static int stbi__jpeg_huff_decode(stbi__jpeg *j, stbi__huffman *h) +{ + unsigned int temp; + int c,k; + + if (j->code_bits < 16) stbi__grow_buffer_unsafe(j); + + // look at the top FAST_BITS and determine what symbol ID it is, + // if the code is <= FAST_BITS + c = (j->code_buffer >> (32 - FAST_BITS)) & ((1 << FAST_BITS)-1); + k = h->fast[c]; + if (k < 255) { + int s = h->size[k]; + if (s > j->code_bits) + return -1; + j->code_buffer <<= s; + j->code_bits -= s; + return h->values[k]; + } + + // naive test is to shift the code_buffer down so k bits are + // valid, then test against maxcode. To speed this up, we've + // preshifted maxcode left so that it has (16-k) 0s at the + // end; in other words, regardless of the number of bits, it + // wants to be compared against something shifted to have 16; + // that way we don't need to shift inside the loop. + temp = j->code_buffer >> 16; + for (k=FAST_BITS+1 ; ; ++k) + if (temp < h->maxcode[k]) + break; + if (k == 17) { + // error! code not found + j->code_bits -= 16; + return -1; + } + + if (k > j->code_bits) + return -1; + + // convert the huffman code to the symbol id + c = ((j->code_buffer >> (32 - k)) & stbi__bmask[k]) + h->delta[k]; + STBI_ASSERT((((j->code_buffer) >> (32 - h->size[c])) & stbi__bmask[h->size[c]]) == h->code[c]); + + // convert the id to a symbol + j->code_bits -= k; + j->code_buffer <<= k; + return h->values[c]; +} + +// bias[n] = (-1<code_bits < n) stbi__grow_buffer_unsafe(j); + + sgn = j->code_buffer >> 31; // sign bit always in MSB; 0 if MSB clear (positive), 1 if MSB set (negative) + k = stbi_lrot(j->code_buffer, n); + j->code_buffer = k & ~stbi__bmask[n]; + k &= stbi__bmask[n]; + j->code_bits -= n; + return k + (stbi__jbias[n] & (sgn - 1)); +} + +// get some unsigned bits +stbi_inline static int stbi__jpeg_get_bits(stbi__jpeg *j, int n) +{ + unsigned int k; + if (j->code_bits < n) stbi__grow_buffer_unsafe(j); + k = stbi_lrot(j->code_buffer, n); + j->code_buffer = k & ~stbi__bmask[n]; + k &= stbi__bmask[n]; + j->code_bits -= n; + return k; +} + +stbi_inline static int stbi__jpeg_get_bit(stbi__jpeg *j) +{ + unsigned int k; + if (j->code_bits < 1) stbi__grow_buffer_unsafe(j); + k = j->code_buffer; + j->code_buffer <<= 1; + --j->code_bits; + return k & 0x80000000; +} + +// given a value that's at position X in the zigzag stream, +// where does it appear in the 8x8 matrix coded as row-major? +static const stbi_uc stbi__jpeg_dezigzag[64+15] = +{ + 0, 1, 8, 16, 9, 2, 3, 10, + 17, 24, 32, 25, 18, 11, 4, 5, + 12, 19, 26, 33, 40, 48, 41, 34, + 27, 20, 13, 6, 7, 14, 21, 28, + 35, 42, 49, 56, 57, 50, 43, 36, + 29, 22, 15, 23, 30, 37, 44, 51, + 58, 59, 52, 45, 38, 31, 39, 46, + 53, 60, 61, 54, 47, 55, 62, 63, + // let corrupt input sample past end + 63, 63, 63, 63, 63, 63, 63, 63, + 63, 63, 63, 63, 63, 63, 63 +}; + +// decode one 64-entry block-- +static int stbi__jpeg_decode_block(stbi__jpeg *j, short data[64], stbi__huffman *hdc, stbi__huffman *hac, stbi__int16 *fac, int b, stbi__uint16 *dequant) +{ + int diff,dc,k; + int t; + + if (j->code_bits < 16) stbi__grow_buffer_unsafe(j); + t = stbi__jpeg_huff_decode(j, hdc); + if (t < 0 || t > 15) return stbi__err("bad huffman code","Corrupt JPEG"); + + // 0 all the ac values now so we can do it 32-bits at a time + memset(data,0,64*sizeof(data[0])); + + diff = t ? stbi__extend_receive(j, t) : 0; + dc = j->img_comp[b].dc_pred + diff; + j->img_comp[b].dc_pred = dc; + data[0] = (short) (dc * dequant[0]); + + // decode AC components, see JPEG spec + k = 1; + do { + unsigned int zig; + int c,r,s; + if (j->code_bits < 16) stbi__grow_buffer_unsafe(j); + c = (j->code_buffer >> (32 - FAST_BITS)) & ((1 << FAST_BITS)-1); + r = fac[c]; + if (r) { // fast-AC path + k += (r >> 4) & 15; // run + s = r & 15; // combined length + j->code_buffer <<= s; + j->code_bits -= s; + // decode into unzigzag'd location + zig = stbi__jpeg_dezigzag[k++]; + data[zig] = (short) ((r >> 8) * dequant[zig]); + } else { + int rs = stbi__jpeg_huff_decode(j, hac); + if (rs < 0) return stbi__err("bad huffman code","Corrupt JPEG"); + s = rs & 15; + r = rs >> 4; + if (s == 0) { + if (rs != 0xf0) break; // end block + k += 16; + } else { + k += r; + // decode into unzigzag'd location + zig = stbi__jpeg_dezigzag[k++]; + data[zig] = (short) (stbi__extend_receive(j,s) * dequant[zig]); + } + } + } while (k < 64); + return 1; +} + +static int stbi__jpeg_decode_block_prog_dc(stbi__jpeg *j, short data[64], stbi__huffman *hdc, int b) +{ + int diff,dc; + int t; + if (j->spec_end != 0) return stbi__err("can't merge dc and ac", "Corrupt JPEG"); + + if (j->code_bits < 16) stbi__grow_buffer_unsafe(j); + + if (j->succ_high == 0) { + // first scan for DC coefficient, must be first + memset(data,0,64*sizeof(data[0])); // 0 all the ac values now + t = stbi__jpeg_huff_decode(j, hdc); + if (t < 0 || t > 15) return stbi__err("can't merge dc and ac", "Corrupt JPEG"); + diff = t ? stbi__extend_receive(j, t) : 0; + + dc = j->img_comp[b].dc_pred + diff; + j->img_comp[b].dc_pred = dc; + data[0] = (short) (dc * (1 << j->succ_low)); + } else { + // refinement scan for DC coefficient + if (stbi__jpeg_get_bit(j)) + data[0] += (short) (1 << j->succ_low); + } + return 1; +} + +// @OPTIMIZE: store non-zigzagged during the decode passes, +// and only de-zigzag when dequantizing +static int stbi__jpeg_decode_block_prog_ac(stbi__jpeg *j, short data[64], stbi__huffman *hac, stbi__int16 *fac) +{ + int k; + if (j->spec_start == 0) return stbi__err("can't merge dc and ac", "Corrupt JPEG"); + + if (j->succ_high == 0) { + int shift = j->succ_low; + + if (j->eob_run) { + --j->eob_run; + return 1; + } + + k = j->spec_start; + do { + unsigned int zig; + int c,r,s; + if (j->code_bits < 16) stbi__grow_buffer_unsafe(j); + c = (j->code_buffer >> (32 - FAST_BITS)) & ((1 << FAST_BITS)-1); + r = fac[c]; + if (r) { // fast-AC path + k += (r >> 4) & 15; // run + s = r & 15; // combined length + j->code_buffer <<= s; + j->code_bits -= s; + zig = stbi__jpeg_dezigzag[k++]; + data[zig] = (short) ((r >> 8) * (1 << shift)); + } else { + int rs = stbi__jpeg_huff_decode(j, hac); + if (rs < 0) return stbi__err("bad huffman code","Corrupt JPEG"); + s = rs & 15; + r = rs >> 4; + if (s == 0) { + if (r < 15) { + j->eob_run = (1 << r); + if (r) + j->eob_run += stbi__jpeg_get_bits(j, r); + --j->eob_run; + break; + } + k += 16; + } else { + k += r; + zig = stbi__jpeg_dezigzag[k++]; + data[zig] = (short) (stbi__extend_receive(j,s) * (1 << shift)); + } + } + } while (k <= j->spec_end); + } else { + // refinement scan for these AC coefficients + + short bit = (short) (1 << j->succ_low); + + if (j->eob_run) { + --j->eob_run; + for (k = j->spec_start; k <= j->spec_end; ++k) { + short *p = &data[stbi__jpeg_dezigzag[k]]; + if (*p != 0) + if (stbi__jpeg_get_bit(j)) + if ((*p & bit)==0) { + if (*p > 0) + *p += bit; + else + *p -= bit; + } + } + } else { + k = j->spec_start; + do { + int r,s; + int rs = stbi__jpeg_huff_decode(j, hac); // @OPTIMIZE see if we can use the fast path here, advance-by-r is so slow, eh + if (rs < 0) return stbi__err("bad huffman code","Corrupt JPEG"); + s = rs & 15; + r = rs >> 4; + if (s == 0) { + if (r < 15) { + j->eob_run = (1 << r) - 1; + if (r) + j->eob_run += stbi__jpeg_get_bits(j, r); + r = 64; // force end of block + } else { + // r=15 s=0 should write 16 0s, so we just do + // a run of 15 0s and then write s (which is 0), + // so we don't have to do anything special here + } + } else { + if (s != 1) return stbi__err("bad huffman code", "Corrupt JPEG"); + // sign bit + if (stbi__jpeg_get_bit(j)) + s = bit; + else + s = -bit; + } + + // advance by r + while (k <= j->spec_end) { + short *p = &data[stbi__jpeg_dezigzag[k++]]; + if (*p != 0) { + if (stbi__jpeg_get_bit(j)) + if ((*p & bit)==0) { + if (*p > 0) + *p += bit; + else + *p -= bit; + } + } else { + if (r == 0) { + *p = (short) s; + break; + } + --r; + } + } + } while (k <= j->spec_end); + } + } + return 1; +} + +// take a -128..127 value and stbi__clamp it and convert to 0..255 +stbi_inline static stbi_uc stbi__clamp(int x) +{ + // trick to use a single test to catch both cases + if ((unsigned int) x > 255) { + if (x < 0) return 0; + if (x > 255) return 255; + } + return (stbi_uc) x; +} + +#define stbi__f2f(x) ((int) (((x) * 4096 + 0.5))) +#define stbi__fsh(x) ((x) * 4096) + +// derived from jidctint -- DCT_ISLOW +#define STBI__IDCT_1D(s0,s1,s2,s3,s4,s5,s6,s7) \ + int t0,t1,t2,t3,p1,p2,p3,p4,p5,x0,x1,x2,x3; \ + p2 = s2; \ + p3 = s6; \ + p1 = (p2+p3) * stbi__f2f(0.5411961f); \ + t2 = p1 + p3*stbi__f2f(-1.847759065f); \ + t3 = p1 + p2*stbi__f2f( 0.765366865f); \ + p2 = s0; \ + p3 = s4; \ + t0 = stbi__fsh(p2+p3); \ + t1 = stbi__fsh(p2-p3); \ + x0 = t0+t3; \ + x3 = t0-t3; \ + x1 = t1+t2; \ + x2 = t1-t2; \ + t0 = s7; \ + t1 = s5; \ + t2 = s3; \ + t3 = s1; \ + p3 = t0+t2; \ + p4 = t1+t3; \ + p1 = t0+t3; \ + p2 = t1+t2; \ + p5 = (p3+p4)*stbi__f2f( 1.175875602f); \ + t0 = t0*stbi__f2f( 0.298631336f); \ + t1 = t1*stbi__f2f( 2.053119869f); \ + t2 = t2*stbi__f2f( 3.072711026f); \ + t3 = t3*stbi__f2f( 1.501321110f); \ + p1 = p5 + p1*stbi__f2f(-0.899976223f); \ + p2 = p5 + p2*stbi__f2f(-2.562915447f); \ + p3 = p3*stbi__f2f(-1.961570560f); \ + p4 = p4*stbi__f2f(-0.390180644f); \ + t3 += p1+p4; \ + t2 += p2+p3; \ + t1 += p2+p4; \ + t0 += p1+p3; + +static void stbi__idct_block(stbi_uc *out, int out_stride, short data[64]) +{ + int i,val[64],*v=val; + stbi_uc *o; + short *d = data; + + // columns + for (i=0; i < 8; ++i,++d, ++v) { + // if all zeroes, shortcut -- this avoids dequantizing 0s and IDCTing + if (d[ 8]==0 && d[16]==0 && d[24]==0 && d[32]==0 + && d[40]==0 && d[48]==0 && d[56]==0) { + // no shortcut 0 seconds + // (1|2|3|4|5|6|7)==0 0 seconds + // all separate -0.047 seconds + // 1 && 2|3 && 4|5 && 6|7: -0.047 seconds + int dcterm = d[0]*4; + v[0] = v[8] = v[16] = v[24] = v[32] = v[40] = v[48] = v[56] = dcterm; + } else { + STBI__IDCT_1D(d[ 0],d[ 8],d[16],d[24],d[32],d[40],d[48],d[56]) + // constants scaled things up by 1<<12; let's bring them back + // down, but keep 2 extra bits of precision + x0 += 512; x1 += 512; x2 += 512; x3 += 512; + v[ 0] = (x0+t3) >> 10; + v[56] = (x0-t3) >> 10; + v[ 8] = (x1+t2) >> 10; + v[48] = (x1-t2) >> 10; + v[16] = (x2+t1) >> 10; + v[40] = (x2-t1) >> 10; + v[24] = (x3+t0) >> 10; + v[32] = (x3-t0) >> 10; + } + } + + for (i=0, v=val, o=out; i < 8; ++i,v+=8,o+=out_stride) { + // no fast case since the first 1D IDCT spread components out + STBI__IDCT_1D(v[0],v[1],v[2],v[3],v[4],v[5],v[6],v[7]) + // constants scaled things up by 1<<12, plus we had 1<<2 from first + // loop, plus horizontal and vertical each scale by sqrt(8) so together + // we've got an extra 1<<3, so 1<<17 total we need to remove. + // so we want to round that, which means adding 0.5 * 1<<17, + // aka 65536. Also, we'll end up with -128 to 127 that we want + // to encode as 0..255 by adding 128, so we'll add that before the shift + x0 += 65536 + (128<<17); + x1 += 65536 + (128<<17); + x2 += 65536 + (128<<17); + x3 += 65536 + (128<<17); + // tried computing the shifts into temps, or'ing the temps to see + // if any were out of range, but that was slower + o[0] = stbi__clamp((x0+t3) >> 17); + o[7] = stbi__clamp((x0-t3) >> 17); + o[1] = stbi__clamp((x1+t2) >> 17); + o[6] = stbi__clamp((x1-t2) >> 17); + o[2] = stbi__clamp((x2+t1) >> 17); + o[5] = stbi__clamp((x2-t1) >> 17); + o[3] = stbi__clamp((x3+t0) >> 17); + o[4] = stbi__clamp((x3-t0) >> 17); + } +} + +#ifdef STBI_SSE2 +// sse2 integer IDCT. not the fastest possible implementation but it +// produces bit-identical results to the generic C version so it's +// fully "transparent". +static void stbi__idct_simd(stbi_uc *out, int out_stride, short data[64]) +{ + // This is constructed to match our regular (generic) integer IDCT exactly. + __m128i row0, row1, row2, row3, row4, row5, row6, row7; + __m128i tmp; + + // dot product constant: even elems=x, odd elems=y + #define dct_const(x,y) _mm_setr_epi16((x),(y),(x),(y),(x),(y),(x),(y)) + + // out(0) = c0[even]*x + c0[odd]*y (c0, x, y 16-bit, out 32-bit) + // out(1) = c1[even]*x + c1[odd]*y + #define dct_rot(out0,out1, x,y,c0,c1) \ + __m128i c0##lo = _mm_unpacklo_epi16((x),(y)); \ + __m128i c0##hi = _mm_unpackhi_epi16((x),(y)); \ + __m128i out0##_l = _mm_madd_epi16(c0##lo, c0); \ + __m128i out0##_h = _mm_madd_epi16(c0##hi, c0); \ + __m128i out1##_l = _mm_madd_epi16(c0##lo, c1); \ + __m128i out1##_h = _mm_madd_epi16(c0##hi, c1) + + // out = in << 12 (in 16-bit, out 32-bit) + #define dct_widen(out, in) \ + __m128i out##_l = _mm_srai_epi32(_mm_unpacklo_epi16(_mm_setzero_si128(), (in)), 4); \ + __m128i out##_h = _mm_srai_epi32(_mm_unpackhi_epi16(_mm_setzero_si128(), (in)), 4) + + // wide add + #define dct_wadd(out, a, b) \ + __m128i out##_l = _mm_add_epi32(a##_l, b##_l); \ + __m128i out##_h = _mm_add_epi32(a##_h, b##_h) + + // wide sub + #define dct_wsub(out, a, b) \ + __m128i out##_l = _mm_sub_epi32(a##_l, b##_l); \ + __m128i out##_h = _mm_sub_epi32(a##_h, b##_h) + + // butterfly a/b, add bias, then shift by "s" and pack + #define dct_bfly32o(out0, out1, a,b,bias,s) \ + { \ + __m128i abiased_l = _mm_add_epi32(a##_l, bias); \ + __m128i abiased_h = _mm_add_epi32(a##_h, bias); \ + dct_wadd(sum, abiased, b); \ + dct_wsub(dif, abiased, b); \ + out0 = _mm_packs_epi32(_mm_srai_epi32(sum_l, s), _mm_srai_epi32(sum_h, s)); \ + out1 = _mm_packs_epi32(_mm_srai_epi32(dif_l, s), _mm_srai_epi32(dif_h, s)); \ + } + + // 8-bit interleave step (for transposes) + #define dct_interleave8(a, b) \ + tmp = a; \ + a = _mm_unpacklo_epi8(a, b); \ + b = _mm_unpackhi_epi8(tmp, b) + + // 16-bit interleave step (for transposes) + #define dct_interleave16(a, b) \ + tmp = a; \ + a = _mm_unpacklo_epi16(a, b); \ + b = _mm_unpackhi_epi16(tmp, b) + + #define dct_pass(bias,shift) \ + { \ + /* even part */ \ + dct_rot(t2e,t3e, row2,row6, rot0_0,rot0_1); \ + __m128i sum04 = _mm_add_epi16(row0, row4); \ + __m128i dif04 = _mm_sub_epi16(row0, row4); \ + dct_widen(t0e, sum04); \ + dct_widen(t1e, dif04); \ + dct_wadd(x0, t0e, t3e); \ + dct_wsub(x3, t0e, t3e); \ + dct_wadd(x1, t1e, t2e); \ + dct_wsub(x2, t1e, t2e); \ + /* odd part */ \ + dct_rot(y0o,y2o, row7,row3, rot2_0,rot2_1); \ + dct_rot(y1o,y3o, row5,row1, rot3_0,rot3_1); \ + __m128i sum17 = _mm_add_epi16(row1, row7); \ + __m128i sum35 = _mm_add_epi16(row3, row5); \ + dct_rot(y4o,y5o, sum17,sum35, rot1_0,rot1_1); \ + dct_wadd(x4, y0o, y4o); \ + dct_wadd(x5, y1o, y5o); \ + dct_wadd(x6, y2o, y5o); \ + dct_wadd(x7, y3o, y4o); \ + dct_bfly32o(row0,row7, x0,x7,bias,shift); \ + dct_bfly32o(row1,row6, x1,x6,bias,shift); \ + dct_bfly32o(row2,row5, x2,x5,bias,shift); \ + dct_bfly32o(row3,row4, x3,x4,bias,shift); \ + } + + __m128i rot0_0 = dct_const(stbi__f2f(0.5411961f), stbi__f2f(0.5411961f) + stbi__f2f(-1.847759065f)); + __m128i rot0_1 = dct_const(stbi__f2f(0.5411961f) + stbi__f2f( 0.765366865f), stbi__f2f(0.5411961f)); + __m128i rot1_0 = dct_const(stbi__f2f(1.175875602f) + stbi__f2f(-0.899976223f), stbi__f2f(1.175875602f)); + __m128i rot1_1 = dct_const(stbi__f2f(1.175875602f), stbi__f2f(1.175875602f) + stbi__f2f(-2.562915447f)); + __m128i rot2_0 = dct_const(stbi__f2f(-1.961570560f) + stbi__f2f( 0.298631336f), stbi__f2f(-1.961570560f)); + __m128i rot2_1 = dct_const(stbi__f2f(-1.961570560f), stbi__f2f(-1.961570560f) + stbi__f2f( 3.072711026f)); + __m128i rot3_0 = dct_const(stbi__f2f(-0.390180644f) + stbi__f2f( 2.053119869f), stbi__f2f(-0.390180644f)); + __m128i rot3_1 = dct_const(stbi__f2f(-0.390180644f), stbi__f2f(-0.390180644f) + stbi__f2f( 1.501321110f)); + + // rounding biases in column/row passes, see stbi__idct_block for explanation. + __m128i bias_0 = _mm_set1_epi32(512); + __m128i bias_1 = _mm_set1_epi32(65536 + (128<<17)); + + // load + row0 = _mm_load_si128((const __m128i *) (data + 0*8)); + row1 = _mm_load_si128((const __m128i *) (data + 1*8)); + row2 = _mm_load_si128((const __m128i *) (data + 2*8)); + row3 = _mm_load_si128((const __m128i *) (data + 3*8)); + row4 = _mm_load_si128((const __m128i *) (data + 4*8)); + row5 = _mm_load_si128((const __m128i *) (data + 5*8)); + row6 = _mm_load_si128((const __m128i *) (data + 6*8)); + row7 = _mm_load_si128((const __m128i *) (data + 7*8)); + + // column pass + dct_pass(bias_0, 10); + + { + // 16bit 8x8 transpose pass 1 + dct_interleave16(row0, row4); + dct_interleave16(row1, row5); + dct_interleave16(row2, row6); + dct_interleave16(row3, row7); + + // transpose pass 2 + dct_interleave16(row0, row2); + dct_interleave16(row1, row3); + dct_interleave16(row4, row6); + dct_interleave16(row5, row7); + + // transpose pass 3 + dct_interleave16(row0, row1); + dct_interleave16(row2, row3); + dct_interleave16(row4, row5); + dct_interleave16(row6, row7); + } + + // row pass + dct_pass(bias_1, 17); + + { + // pack + __m128i p0 = _mm_packus_epi16(row0, row1); // a0a1a2a3...a7b0b1b2b3...b7 + __m128i p1 = _mm_packus_epi16(row2, row3); + __m128i p2 = _mm_packus_epi16(row4, row5); + __m128i p3 = _mm_packus_epi16(row6, row7); + + // 8bit 8x8 transpose pass 1 + dct_interleave8(p0, p2); // a0e0a1e1... + dct_interleave8(p1, p3); // c0g0c1g1... + + // transpose pass 2 + dct_interleave8(p0, p1); // a0c0e0g0... + dct_interleave8(p2, p3); // b0d0f0h0... + + // transpose pass 3 + dct_interleave8(p0, p2); // a0b0c0d0... + dct_interleave8(p1, p3); // a4b4c4d4... + + // store + _mm_storel_epi64((__m128i *) out, p0); out += out_stride; + _mm_storel_epi64((__m128i *) out, _mm_shuffle_epi32(p0, 0x4e)); out += out_stride; + _mm_storel_epi64((__m128i *) out, p2); out += out_stride; + _mm_storel_epi64((__m128i *) out, _mm_shuffle_epi32(p2, 0x4e)); out += out_stride; + _mm_storel_epi64((__m128i *) out, p1); out += out_stride; + _mm_storel_epi64((__m128i *) out, _mm_shuffle_epi32(p1, 0x4e)); out += out_stride; + _mm_storel_epi64((__m128i *) out, p3); out += out_stride; + _mm_storel_epi64((__m128i *) out, _mm_shuffle_epi32(p3, 0x4e)); + } + +#undef dct_const +#undef dct_rot +#undef dct_widen +#undef dct_wadd +#undef dct_wsub +#undef dct_bfly32o +#undef dct_interleave8 +#undef dct_interleave16 +#undef dct_pass +} + +#endif // STBI_SSE2 + +#ifdef STBI_NEON + +// NEON integer IDCT. should produce bit-identical +// results to the generic C version. +static void stbi__idct_simd(stbi_uc *out, int out_stride, short data[64]) +{ + int16x8_t row0, row1, row2, row3, row4, row5, row6, row7; + + int16x4_t rot0_0 = vdup_n_s16(stbi__f2f(0.5411961f)); + int16x4_t rot0_1 = vdup_n_s16(stbi__f2f(-1.847759065f)); + int16x4_t rot0_2 = vdup_n_s16(stbi__f2f( 0.765366865f)); + int16x4_t rot1_0 = vdup_n_s16(stbi__f2f( 1.175875602f)); + int16x4_t rot1_1 = vdup_n_s16(stbi__f2f(-0.899976223f)); + int16x4_t rot1_2 = vdup_n_s16(stbi__f2f(-2.562915447f)); + int16x4_t rot2_0 = vdup_n_s16(stbi__f2f(-1.961570560f)); + int16x4_t rot2_1 = vdup_n_s16(stbi__f2f(-0.390180644f)); + int16x4_t rot3_0 = vdup_n_s16(stbi__f2f( 0.298631336f)); + int16x4_t rot3_1 = vdup_n_s16(stbi__f2f( 2.053119869f)); + int16x4_t rot3_2 = vdup_n_s16(stbi__f2f( 3.072711026f)); + int16x4_t rot3_3 = vdup_n_s16(stbi__f2f( 1.501321110f)); + +#define dct_long_mul(out, inq, coeff) \ + int32x4_t out##_l = vmull_s16(vget_low_s16(inq), coeff); \ + int32x4_t out##_h = vmull_s16(vget_high_s16(inq), coeff) + +#define dct_long_mac(out, acc, inq, coeff) \ + int32x4_t out##_l = vmlal_s16(acc##_l, vget_low_s16(inq), coeff); \ + int32x4_t out##_h = vmlal_s16(acc##_h, vget_high_s16(inq), coeff) + +#define dct_widen(out, inq) \ + int32x4_t out##_l = vshll_n_s16(vget_low_s16(inq), 12); \ + int32x4_t out##_h = vshll_n_s16(vget_high_s16(inq), 12) + +// wide add +#define dct_wadd(out, a, b) \ + int32x4_t out##_l = vaddq_s32(a##_l, b##_l); \ + int32x4_t out##_h = vaddq_s32(a##_h, b##_h) + +// wide sub +#define dct_wsub(out, a, b) \ + int32x4_t out##_l = vsubq_s32(a##_l, b##_l); \ + int32x4_t out##_h = vsubq_s32(a##_h, b##_h) + +// butterfly a/b, then shift using "shiftop" by "s" and pack +#define dct_bfly32o(out0,out1, a,b,shiftop,s) \ + { \ + dct_wadd(sum, a, b); \ + dct_wsub(dif, a, b); \ + out0 = vcombine_s16(shiftop(sum_l, s), shiftop(sum_h, s)); \ + out1 = vcombine_s16(shiftop(dif_l, s), shiftop(dif_h, s)); \ + } + +#define dct_pass(shiftop, shift) \ + { \ + /* even part */ \ + int16x8_t sum26 = vaddq_s16(row2, row6); \ + dct_long_mul(p1e, sum26, rot0_0); \ + dct_long_mac(t2e, p1e, row6, rot0_1); \ + dct_long_mac(t3e, p1e, row2, rot0_2); \ + int16x8_t sum04 = vaddq_s16(row0, row4); \ + int16x8_t dif04 = vsubq_s16(row0, row4); \ + dct_widen(t0e, sum04); \ + dct_widen(t1e, dif04); \ + dct_wadd(x0, t0e, t3e); \ + dct_wsub(x3, t0e, t3e); \ + dct_wadd(x1, t1e, t2e); \ + dct_wsub(x2, t1e, t2e); \ + /* odd part */ \ + int16x8_t sum15 = vaddq_s16(row1, row5); \ + int16x8_t sum17 = vaddq_s16(row1, row7); \ + int16x8_t sum35 = vaddq_s16(row3, row5); \ + int16x8_t sum37 = vaddq_s16(row3, row7); \ + int16x8_t sumodd = vaddq_s16(sum17, sum35); \ + dct_long_mul(p5o, sumodd, rot1_0); \ + dct_long_mac(p1o, p5o, sum17, rot1_1); \ + dct_long_mac(p2o, p5o, sum35, rot1_2); \ + dct_long_mul(p3o, sum37, rot2_0); \ + dct_long_mul(p4o, sum15, rot2_1); \ + dct_wadd(sump13o, p1o, p3o); \ + dct_wadd(sump24o, p2o, p4o); \ + dct_wadd(sump23o, p2o, p3o); \ + dct_wadd(sump14o, p1o, p4o); \ + dct_long_mac(x4, sump13o, row7, rot3_0); \ + dct_long_mac(x5, sump24o, row5, rot3_1); \ + dct_long_mac(x6, sump23o, row3, rot3_2); \ + dct_long_mac(x7, sump14o, row1, rot3_3); \ + dct_bfly32o(row0,row7, x0,x7,shiftop,shift); \ + dct_bfly32o(row1,row6, x1,x6,shiftop,shift); \ + dct_bfly32o(row2,row5, x2,x5,shiftop,shift); \ + dct_bfly32o(row3,row4, x3,x4,shiftop,shift); \ + } + + // load + row0 = vld1q_s16(data + 0*8); + row1 = vld1q_s16(data + 1*8); + row2 = vld1q_s16(data + 2*8); + row3 = vld1q_s16(data + 3*8); + row4 = vld1q_s16(data + 4*8); + row5 = vld1q_s16(data + 5*8); + row6 = vld1q_s16(data + 6*8); + row7 = vld1q_s16(data + 7*8); + + // add DC bias + row0 = vaddq_s16(row0, vsetq_lane_s16(1024, vdupq_n_s16(0), 0)); + + // column pass + dct_pass(vrshrn_n_s32, 10); + + // 16bit 8x8 transpose + { +// these three map to a single VTRN.16, VTRN.32, and VSWP, respectively. +// whether compilers actually get this is another story, sadly. +#define dct_trn16(x, y) { int16x8x2_t t = vtrnq_s16(x, y); x = t.val[0]; y = t.val[1]; } +#define dct_trn32(x, y) { int32x4x2_t t = vtrnq_s32(vreinterpretq_s32_s16(x), vreinterpretq_s32_s16(y)); x = vreinterpretq_s16_s32(t.val[0]); y = vreinterpretq_s16_s32(t.val[1]); } +#define dct_trn64(x, y) { int16x8_t x0 = x; int16x8_t y0 = y; x = vcombine_s16(vget_low_s16(x0), vget_low_s16(y0)); y = vcombine_s16(vget_high_s16(x0), vget_high_s16(y0)); } + + // pass 1 + dct_trn16(row0, row1); // a0b0a2b2a4b4a6b6 + dct_trn16(row2, row3); + dct_trn16(row4, row5); + dct_trn16(row6, row7); + + // pass 2 + dct_trn32(row0, row2); // a0b0c0d0a4b4c4d4 + dct_trn32(row1, row3); + dct_trn32(row4, row6); + dct_trn32(row5, row7); + + // pass 3 + dct_trn64(row0, row4); // a0b0c0d0e0f0g0h0 + dct_trn64(row1, row5); + dct_trn64(row2, row6); + dct_trn64(row3, row7); + +#undef dct_trn16 +#undef dct_trn32 +#undef dct_trn64 + } + + // row pass + // vrshrn_n_s32 only supports shifts up to 16, we need + // 17. so do a non-rounding shift of 16 first then follow + // up with a rounding shift by 1. + dct_pass(vshrn_n_s32, 16); + + { + // pack and round + uint8x8_t p0 = vqrshrun_n_s16(row0, 1); + uint8x8_t p1 = vqrshrun_n_s16(row1, 1); + uint8x8_t p2 = vqrshrun_n_s16(row2, 1); + uint8x8_t p3 = vqrshrun_n_s16(row3, 1); + uint8x8_t p4 = vqrshrun_n_s16(row4, 1); + uint8x8_t p5 = vqrshrun_n_s16(row5, 1); + uint8x8_t p6 = vqrshrun_n_s16(row6, 1); + uint8x8_t p7 = vqrshrun_n_s16(row7, 1); + + // again, these can translate into one instruction, but often don't. +#define dct_trn8_8(x, y) { uint8x8x2_t t = vtrn_u8(x, y); x = t.val[0]; y = t.val[1]; } +#define dct_trn8_16(x, y) { uint16x4x2_t t = vtrn_u16(vreinterpret_u16_u8(x), vreinterpret_u16_u8(y)); x = vreinterpret_u8_u16(t.val[0]); y = vreinterpret_u8_u16(t.val[1]); } +#define dct_trn8_32(x, y) { uint32x2x2_t t = vtrn_u32(vreinterpret_u32_u8(x), vreinterpret_u32_u8(y)); x = vreinterpret_u8_u32(t.val[0]); y = vreinterpret_u8_u32(t.val[1]); } + + // sadly can't use interleaved stores here since we only write + // 8 bytes to each scan line! + + // 8x8 8-bit transpose pass 1 + dct_trn8_8(p0, p1); + dct_trn8_8(p2, p3); + dct_trn8_8(p4, p5); + dct_trn8_8(p6, p7); + + // pass 2 + dct_trn8_16(p0, p2); + dct_trn8_16(p1, p3); + dct_trn8_16(p4, p6); + dct_trn8_16(p5, p7); + + // pass 3 + dct_trn8_32(p0, p4); + dct_trn8_32(p1, p5); + dct_trn8_32(p2, p6); + dct_trn8_32(p3, p7); + + // store + vst1_u8(out, p0); out += out_stride; + vst1_u8(out, p1); out += out_stride; + vst1_u8(out, p2); out += out_stride; + vst1_u8(out, p3); out += out_stride; + vst1_u8(out, p4); out += out_stride; + vst1_u8(out, p5); out += out_stride; + vst1_u8(out, p6); out += out_stride; + vst1_u8(out, p7); + +#undef dct_trn8_8 +#undef dct_trn8_16 +#undef dct_trn8_32 + } + +#undef dct_long_mul +#undef dct_long_mac +#undef dct_widen +#undef dct_wadd +#undef dct_wsub +#undef dct_bfly32o +#undef dct_pass +} + +#endif // STBI_NEON + +#define STBI__MARKER_none 0xff +// if there's a pending marker from the entropy stream, return that +// otherwise, fetch from the stream and get a marker. if there's no +// marker, return 0xff, which is never a valid marker value +static stbi_uc stbi__get_marker(stbi__jpeg *j) +{ + stbi_uc x; + if (j->marker != STBI__MARKER_none) { x = j->marker; j->marker = STBI__MARKER_none; return x; } + x = stbi__get8(j->s); + if (x != 0xff) return STBI__MARKER_none; + while (x == 0xff) + x = stbi__get8(j->s); // consume repeated 0xff fill bytes + return x; +} + +// in each scan, we'll have scan_n components, and the order +// of the components is specified by order[] +#define STBI__RESTART(x) ((x) >= 0xd0 && (x) <= 0xd7) + +// after a restart interval, stbi__jpeg_reset the entropy decoder and +// the dc prediction +static void stbi__jpeg_reset(stbi__jpeg *j) +{ + j->code_bits = 0; + j->code_buffer = 0; + j->nomore = 0; + j->img_comp[0].dc_pred = j->img_comp[1].dc_pred = j->img_comp[2].dc_pred = j->img_comp[3].dc_pred = 0; + j->marker = STBI__MARKER_none; + j->todo = j->restart_interval ? j->restart_interval : 0x7fffffff; + j->eob_run = 0; + // no more than 1<<31 MCUs if no restart_interal? that's plenty safe, + // since we don't even allow 1<<30 pixels +} + +static int stbi__parse_entropy_coded_data(stbi__jpeg *z) +{ + stbi__jpeg_reset(z); + if (!z->progressive) { + if (z->scan_n == 1) { + int i,j; + STBI_SIMD_ALIGN(short, data[64]); + int n = z->order[0]; + // non-interleaved data, we just need to process one block at a time, + // in trivial scanline order + // number of blocks to do just depends on how many actual "pixels" this + // component has, independent of interleaved MCU blocking and such + int w = (z->img_comp[n].x+7) >> 3; + int h = (z->img_comp[n].y+7) >> 3; + for (j=0; j < h; ++j) { + for (i=0; i < w; ++i) { + int ha = z->img_comp[n].ha; + if (!stbi__jpeg_decode_block(z, data, z->huff_dc+z->img_comp[n].hd, z->huff_ac+ha, z->fast_ac[ha], n, z->dequant[z->img_comp[n].tq])) return 0; + z->idct_block_kernel(z->img_comp[n].data+z->img_comp[n].w2*j*8+i*8, z->img_comp[n].w2, data); + // every data block is an MCU, so countdown the restart interval + if (--z->todo <= 0) { + if (z->code_bits < 24) stbi__grow_buffer_unsafe(z); + // if it's NOT a restart, then just bail, so we get corrupt data + // rather than no data + if (!STBI__RESTART(z->marker)) return 1; + stbi__jpeg_reset(z); + } + } + } + return 1; + } else { // interleaved + int i,j,k,x,y; + STBI_SIMD_ALIGN(short, data[64]); + for (j=0; j < z->img_mcu_y; ++j) { + for (i=0; i < z->img_mcu_x; ++i) { + // scan an interleaved mcu... process scan_n components in order + for (k=0; k < z->scan_n; ++k) { + int n = z->order[k]; + // scan out an mcu's worth of this component; that's just determined + // by the basic H and V specified for the component + for (y=0; y < z->img_comp[n].v; ++y) { + for (x=0; x < z->img_comp[n].h; ++x) { + int x2 = (i*z->img_comp[n].h + x)*8; + int y2 = (j*z->img_comp[n].v + y)*8; + int ha = z->img_comp[n].ha; + if (!stbi__jpeg_decode_block(z, data, z->huff_dc+z->img_comp[n].hd, z->huff_ac+ha, z->fast_ac[ha], n, z->dequant[z->img_comp[n].tq])) return 0; + z->idct_block_kernel(z->img_comp[n].data+z->img_comp[n].w2*y2+x2, z->img_comp[n].w2, data); + } + } + } + // after all interleaved components, that's an interleaved MCU, + // so now count down the restart interval + if (--z->todo <= 0) { + if (z->code_bits < 24) stbi__grow_buffer_unsafe(z); + if (!STBI__RESTART(z->marker)) return 1; + stbi__jpeg_reset(z); + } + } + } + return 1; + } + } else { + if (z->scan_n == 1) { + int i,j; + int n = z->order[0]; + // non-interleaved data, we just need to process one block at a time, + // in trivial scanline order + // number of blocks to do just depends on how many actual "pixels" this + // component has, independent of interleaved MCU blocking and such + int w = (z->img_comp[n].x+7) >> 3; + int h = (z->img_comp[n].y+7) >> 3; + for (j=0; j < h; ++j) { + for (i=0; i < w; ++i) { + short *data = z->img_comp[n].coeff + 64 * (i + j * z->img_comp[n].coeff_w); + if (z->spec_start == 0) { + if (!stbi__jpeg_decode_block_prog_dc(z, data, &z->huff_dc[z->img_comp[n].hd], n)) + return 0; + } else { + int ha = z->img_comp[n].ha; + if (!stbi__jpeg_decode_block_prog_ac(z, data, &z->huff_ac[ha], z->fast_ac[ha])) + return 0; + } + // every data block is an MCU, so countdown the restart interval + if (--z->todo <= 0) { + if (z->code_bits < 24) stbi__grow_buffer_unsafe(z); + if (!STBI__RESTART(z->marker)) return 1; + stbi__jpeg_reset(z); + } + } + } + return 1; + } else { // interleaved + int i,j,k,x,y; + for (j=0; j < z->img_mcu_y; ++j) { + for (i=0; i < z->img_mcu_x; ++i) { + // scan an interleaved mcu... process scan_n components in order + for (k=0; k < z->scan_n; ++k) { + int n = z->order[k]; + // scan out an mcu's worth of this component; that's just determined + // by the basic H and V specified for the component + for (y=0; y < z->img_comp[n].v; ++y) { + for (x=0; x < z->img_comp[n].h; ++x) { + int x2 = (i*z->img_comp[n].h + x); + int y2 = (j*z->img_comp[n].v + y); + short *data = z->img_comp[n].coeff + 64 * (x2 + y2 * z->img_comp[n].coeff_w); + if (!stbi__jpeg_decode_block_prog_dc(z, data, &z->huff_dc[z->img_comp[n].hd], n)) + return 0; + } + } + } + // after all interleaved components, that's an interleaved MCU, + // so now count down the restart interval + if (--z->todo <= 0) { + if (z->code_bits < 24) stbi__grow_buffer_unsafe(z); + if (!STBI__RESTART(z->marker)) return 1; + stbi__jpeg_reset(z); + } + } + } + return 1; + } + } +} + +static void stbi__jpeg_dequantize(short *data, stbi__uint16 *dequant) +{ + int i; + for (i=0; i < 64; ++i) + data[i] *= dequant[i]; +} + +static void stbi__jpeg_finish(stbi__jpeg *z) +{ + if (z->progressive) { + // dequantize and idct the data + int i,j,n; + for (n=0; n < z->s->img_n; ++n) { + int w = (z->img_comp[n].x+7) >> 3; + int h = (z->img_comp[n].y+7) >> 3; + for (j=0; j < h; ++j) { + for (i=0; i < w; ++i) { + short *data = z->img_comp[n].coeff + 64 * (i + j * z->img_comp[n].coeff_w); + stbi__jpeg_dequantize(data, z->dequant[z->img_comp[n].tq]); + z->idct_block_kernel(z->img_comp[n].data+z->img_comp[n].w2*j*8+i*8, z->img_comp[n].w2, data); + } + } + } + } +} + +static int stbi__process_marker(stbi__jpeg *z, int m) +{ + int L; + switch (m) { + case STBI__MARKER_none: // no marker found + return stbi__err("expected marker","Corrupt JPEG"); + + case 0xDD: // DRI - specify restart interval + if (stbi__get16be(z->s) != 4) return stbi__err("bad DRI len","Corrupt JPEG"); + z->restart_interval = stbi__get16be(z->s); + return 1; + + case 0xDB: // DQT - define quantization table + L = stbi__get16be(z->s)-2; + while (L > 0) { + int q = stbi__get8(z->s); + int p = q >> 4, sixteen = (p != 0); + int t = q & 15,i; + if (p != 0 && p != 1) return stbi__err("bad DQT type","Corrupt JPEG"); + if (t > 3) return stbi__err("bad DQT table","Corrupt JPEG"); + + for (i=0; i < 64; ++i) + z->dequant[t][stbi__jpeg_dezigzag[i]] = (stbi__uint16)(sixteen ? stbi__get16be(z->s) : stbi__get8(z->s)); + L -= (sixteen ? 129 : 65); + } + return L==0; + + case 0xC4: // DHT - define huffman table + L = stbi__get16be(z->s)-2; + while (L > 0) { + stbi_uc *v; + int sizes[16],i,n=0; + int q = stbi__get8(z->s); + int tc = q >> 4; + int th = q & 15; + if (tc > 1 || th > 3) return stbi__err("bad DHT header","Corrupt JPEG"); + for (i=0; i < 16; ++i) { + sizes[i] = stbi__get8(z->s); + n += sizes[i]; + } + L -= 17; + if (tc == 0) { + if (!stbi__build_huffman(z->huff_dc+th, sizes)) return 0; + v = z->huff_dc[th].values; + } else { + if (!stbi__build_huffman(z->huff_ac+th, sizes)) return 0; + v = z->huff_ac[th].values; + } + for (i=0; i < n; ++i) + v[i] = stbi__get8(z->s); + if (tc != 0) + stbi__build_fast_ac(z->fast_ac[th], z->huff_ac + th); + L -= n; + } + return L==0; + } + + // check for comment block or APP blocks + if ((m >= 0xE0 && m <= 0xEF) || m == 0xFE) { + L = stbi__get16be(z->s); + if (L < 2) { + if (m == 0xFE) + return stbi__err("bad COM len","Corrupt JPEG"); + else + return stbi__err("bad APP len","Corrupt JPEG"); + } + L -= 2; + + if (m == 0xE0 && L >= 5) { // JFIF APP0 segment + static const unsigned char tag[5] = {'J','F','I','F','\0'}; + int ok = 1; + int i; + for (i=0; i < 5; ++i) + if (stbi__get8(z->s) != tag[i]) + ok = 0; + L -= 5; + if (ok) + z->jfif = 1; + } else if (m == 0xEE && L >= 12) { // Adobe APP14 segment + static const unsigned char tag[6] = {'A','d','o','b','e','\0'}; + int ok = 1; + int i; + for (i=0; i < 6; ++i) + if (stbi__get8(z->s) != tag[i]) + ok = 0; + L -= 6; + if (ok) { + stbi__get8(z->s); // version + stbi__get16be(z->s); // flags0 + stbi__get16be(z->s); // flags1 + z->app14_color_transform = stbi__get8(z->s); // color transform + L -= 6; + } + } + + stbi__skip(z->s, L); + return 1; + } + + return stbi__err("unknown marker","Corrupt JPEG"); +} + +// after we see SOS +static int stbi__process_scan_header(stbi__jpeg *z) +{ + int i; + int Ls = stbi__get16be(z->s); + z->scan_n = stbi__get8(z->s); + if (z->scan_n < 1 || z->scan_n > 4 || z->scan_n > (int) z->s->img_n) return stbi__err("bad SOS component count","Corrupt JPEG"); + if (Ls != 6+2*z->scan_n) return stbi__err("bad SOS len","Corrupt JPEG"); + for (i=0; i < z->scan_n; ++i) { + int id = stbi__get8(z->s), which; + int q = stbi__get8(z->s); + for (which = 0; which < z->s->img_n; ++which) + if (z->img_comp[which].id == id) + break; + if (which == z->s->img_n) return 0; // no match + z->img_comp[which].hd = q >> 4; if (z->img_comp[which].hd > 3) return stbi__err("bad DC huff","Corrupt JPEG"); + z->img_comp[which].ha = q & 15; if (z->img_comp[which].ha > 3) return stbi__err("bad AC huff","Corrupt JPEG"); + z->order[i] = which; + } + + { + int aa; + z->spec_start = stbi__get8(z->s); + z->spec_end = stbi__get8(z->s); // should be 63, but might be 0 + aa = stbi__get8(z->s); + z->succ_high = (aa >> 4); + z->succ_low = (aa & 15); + if (z->progressive) { + if (z->spec_start > 63 || z->spec_end > 63 || z->spec_start > z->spec_end || z->succ_high > 13 || z->succ_low > 13) + return stbi__err("bad SOS", "Corrupt JPEG"); + } else { + if (z->spec_start != 0) return stbi__err("bad SOS","Corrupt JPEG"); + if (z->succ_high != 0 || z->succ_low != 0) return stbi__err("bad SOS","Corrupt JPEG"); + z->spec_end = 63; + } + } + + return 1; +} + +static int stbi__free_jpeg_components(stbi__jpeg *z, int ncomp, int why) +{ + int i; + for (i=0; i < ncomp; ++i) { + if (z->img_comp[i].raw_data) { + STBI_FREE(z->img_comp[i].raw_data); + z->img_comp[i].raw_data = NULL; + z->img_comp[i].data = NULL; + } + if (z->img_comp[i].raw_coeff) { + STBI_FREE(z->img_comp[i].raw_coeff); + z->img_comp[i].raw_coeff = 0; + z->img_comp[i].coeff = 0; + } + if (z->img_comp[i].linebuf) { + STBI_FREE(z->img_comp[i].linebuf); + z->img_comp[i].linebuf = NULL; + } + } + return why; +} + +static int stbi__process_frame_header(stbi__jpeg *z, int scan) +{ + stbi__context *s = z->s; + int Lf,p,i,q, h_max=1,v_max=1,c; + Lf = stbi__get16be(s); if (Lf < 11) return stbi__err("bad SOF len","Corrupt JPEG"); // JPEG + p = stbi__get8(s); if (p != 8) return stbi__err("only 8-bit","JPEG format not supported: 8-bit only"); // JPEG baseline + s->img_y = stbi__get16be(s); if (s->img_y == 0) return stbi__err("no header height", "JPEG format not supported: delayed height"); // Legal, but we don't handle it--but neither does IJG + s->img_x = stbi__get16be(s); if (s->img_x == 0) return stbi__err("0 width","Corrupt JPEG"); // JPEG requires + if (s->img_y > STBI_MAX_DIMENSIONS) return stbi__err("too large","Very large image (corrupt?)"); + if (s->img_x > STBI_MAX_DIMENSIONS) return stbi__err("too large","Very large image (corrupt?)"); + c = stbi__get8(s); + if (c != 3 && c != 1 && c != 4) return stbi__err("bad component count","Corrupt JPEG"); + s->img_n = c; + for (i=0; i < c; ++i) { + z->img_comp[i].data = NULL; + z->img_comp[i].linebuf = NULL; + } + + if (Lf != 8+3*s->img_n) return stbi__err("bad SOF len","Corrupt JPEG"); + + z->rgb = 0; + for (i=0; i < s->img_n; ++i) { + static const unsigned char rgb[3] = { 'R', 'G', 'B' }; + z->img_comp[i].id = stbi__get8(s); + if (s->img_n == 3 && z->img_comp[i].id == rgb[i]) + ++z->rgb; + q = stbi__get8(s); + z->img_comp[i].h = (q >> 4); if (!z->img_comp[i].h || z->img_comp[i].h > 4) return stbi__err("bad H","Corrupt JPEG"); + z->img_comp[i].v = q & 15; if (!z->img_comp[i].v || z->img_comp[i].v > 4) return stbi__err("bad V","Corrupt JPEG"); + z->img_comp[i].tq = stbi__get8(s); if (z->img_comp[i].tq > 3) return stbi__err("bad TQ","Corrupt JPEG"); + } + + if (scan != STBI__SCAN_load) return 1; + + if (!stbi__mad3sizes_valid(s->img_x, s->img_y, s->img_n, 0)) return stbi__err("too large", "Image too large to decode"); + + for (i=0; i < s->img_n; ++i) { + if (z->img_comp[i].h > h_max) h_max = z->img_comp[i].h; + if (z->img_comp[i].v > v_max) v_max = z->img_comp[i].v; + } + + // check that plane subsampling factors are integer ratios; our resamplers can't deal with fractional ratios + // and I've never seen a non-corrupted JPEG file actually use them + for (i=0; i < s->img_n; ++i) { + if (h_max % z->img_comp[i].h != 0) return stbi__err("bad H","Corrupt JPEG"); + if (v_max % z->img_comp[i].v != 0) return stbi__err("bad V","Corrupt JPEG"); + } + + // compute interleaved mcu info + z->img_h_max = h_max; + z->img_v_max = v_max; + z->img_mcu_w = h_max * 8; + z->img_mcu_h = v_max * 8; + // these sizes can't be more than 17 bits + z->img_mcu_x = (s->img_x + z->img_mcu_w-1) / z->img_mcu_w; + z->img_mcu_y = (s->img_y + z->img_mcu_h-1) / z->img_mcu_h; + + for (i=0; i < s->img_n; ++i) { + // number of effective pixels (e.g. for non-interleaved MCU) + z->img_comp[i].x = (s->img_x * z->img_comp[i].h + h_max-1) / h_max; + z->img_comp[i].y = (s->img_y * z->img_comp[i].v + v_max-1) / v_max; + // to simplify generation, we'll allocate enough memory to decode + // the bogus oversized data from using interleaved MCUs and their + // big blocks (e.g. a 16x16 iMCU on an image of width 33); we won't + // discard the extra data until colorspace conversion + // + // img_mcu_x, img_mcu_y: <=17 bits; comp[i].h and .v are <=4 (checked earlier) + // so these muls can't overflow with 32-bit ints (which we require) + z->img_comp[i].w2 = z->img_mcu_x * z->img_comp[i].h * 8; + z->img_comp[i].h2 = z->img_mcu_y * z->img_comp[i].v * 8; + z->img_comp[i].coeff = 0; + z->img_comp[i].raw_coeff = 0; + z->img_comp[i].linebuf = NULL; + z->img_comp[i].raw_data = stbi__malloc_mad2(z->img_comp[i].w2, z->img_comp[i].h2, 15); + if (z->img_comp[i].raw_data == NULL) + return stbi__free_jpeg_components(z, i+1, stbi__err("outofmem", "Out of memory")); + // align blocks for idct using mmx/sse + z->img_comp[i].data = (stbi_uc*) (((size_t) z->img_comp[i].raw_data + 15) & ~15); + if (z->progressive) { + // w2, h2 are multiples of 8 (see above) + z->img_comp[i].coeff_w = z->img_comp[i].w2 / 8; + z->img_comp[i].coeff_h = z->img_comp[i].h2 / 8; + z->img_comp[i].raw_coeff = stbi__malloc_mad3(z->img_comp[i].w2, z->img_comp[i].h2, sizeof(short), 15); + if (z->img_comp[i].raw_coeff == NULL) + return stbi__free_jpeg_components(z, i+1, stbi__err("outofmem", "Out of memory")); + z->img_comp[i].coeff = (short*) (((size_t) z->img_comp[i].raw_coeff + 15) & ~15); + } + } + + return 1; +} + +// use comparisons since in some cases we handle more than one case (e.g. SOF) +#define stbi__DNL(x) ((x) == 0xdc) +#define stbi__SOI(x) ((x) == 0xd8) +#define stbi__EOI(x) ((x) == 0xd9) +#define stbi__SOF(x) ((x) == 0xc0 || (x) == 0xc1 || (x) == 0xc2) +#define stbi__SOS(x) ((x) == 0xda) + +#define stbi__SOF_progressive(x) ((x) == 0xc2) + +static int stbi__decode_jpeg_header(stbi__jpeg *z, int scan) +{ + int m; + z->jfif = 0; + z->app14_color_transform = -1; // valid values are 0,1,2 + z->marker = STBI__MARKER_none; // initialize cached marker to empty + m = stbi__get_marker(z); + if (!stbi__SOI(m)) return stbi__err("no SOI","Corrupt JPEG"); + if (scan == STBI__SCAN_type) return 1; + m = stbi__get_marker(z); + while (!stbi__SOF(m)) { + if (!stbi__process_marker(z,m)) return 0; + m = stbi__get_marker(z); + while (m == STBI__MARKER_none) { + // some files have extra padding after their blocks, so ok, we'll scan + if (stbi__at_eof(z->s)) return stbi__err("no SOF", "Corrupt JPEG"); + m = stbi__get_marker(z); + } + } + z->progressive = stbi__SOF_progressive(m); + if (!stbi__process_frame_header(z, scan)) return 0; + return 1; +} + +// decode image to YCbCr format +static int stbi__decode_jpeg_image(stbi__jpeg *j) +{ + int m; + for (m = 0; m < 4; m++) { + j->img_comp[m].raw_data = NULL; + j->img_comp[m].raw_coeff = NULL; + } + j->restart_interval = 0; + if (!stbi__decode_jpeg_header(j, STBI__SCAN_load)) return 0; + m = stbi__get_marker(j); + while (!stbi__EOI(m)) { + if (stbi__SOS(m)) { + if (!stbi__process_scan_header(j)) return 0; + if (!stbi__parse_entropy_coded_data(j)) return 0; + if (j->marker == STBI__MARKER_none ) { + // handle 0s at the end of image data from IP Kamera 9060 + while (!stbi__at_eof(j->s)) { + int x = stbi__get8(j->s); + if (x == 255) { + j->marker = stbi__get8(j->s); + break; + } + } + // if we reach eof without hitting a marker, stbi__get_marker() below will fail and we'll eventually return 0 + } + } else if (stbi__DNL(m)) { + int Ld = stbi__get16be(j->s); + stbi__uint32 NL = stbi__get16be(j->s); + if (Ld != 4) return stbi__err("bad DNL len", "Corrupt JPEG"); + if (NL != j->s->img_y) return stbi__err("bad DNL height", "Corrupt JPEG"); + } else { + if (!stbi__process_marker(j, m)) return 0; + } + m = stbi__get_marker(j); + } + if (j->progressive) + stbi__jpeg_finish(j); + return 1; +} + +// static jfif-centered resampling (across block boundaries) + +typedef stbi_uc *(*resample_row_func)(stbi_uc *out, stbi_uc *in0, stbi_uc *in1, + int w, int hs); + +#define stbi__div4(x) ((stbi_uc) ((x) >> 2)) + +static stbi_uc *resample_row_1(stbi_uc *out, stbi_uc *in_near, stbi_uc *in_far, int w, int hs) +{ + STBI_NOTUSED(out); + STBI_NOTUSED(in_far); + STBI_NOTUSED(w); + STBI_NOTUSED(hs); + return in_near; +} + +static stbi_uc* stbi__resample_row_v_2(stbi_uc *out, stbi_uc *in_near, stbi_uc *in_far, int w, int hs) +{ + // need to generate two samples vertically for every one in input + int i; + STBI_NOTUSED(hs); + for (i=0; i < w; ++i) + out[i] = stbi__div4(3*in_near[i] + in_far[i] + 2); + return out; +} + +static stbi_uc* stbi__resample_row_h_2(stbi_uc *out, stbi_uc *in_near, stbi_uc *in_far, int w, int hs) +{ + // need to generate two samples horizontally for every one in input + int i; + stbi_uc *input = in_near; + + if (w == 1) { + // if only one sample, can't do any interpolation + out[0] = out[1] = input[0]; + return out; + } + + out[0] = input[0]; + out[1] = stbi__div4(input[0]*3 + input[1] + 2); + for (i=1; i < w-1; ++i) { + int n = 3*input[i]+2; + out[i*2+0] = stbi__div4(n+input[i-1]); + out[i*2+1] = stbi__div4(n+input[i+1]); + } + out[i*2+0] = stbi__div4(input[w-2]*3 + input[w-1] + 2); + out[i*2+1] = input[w-1]; + + STBI_NOTUSED(in_far); + STBI_NOTUSED(hs); + + return out; +} + +#define stbi__div16(x) ((stbi_uc) ((x) >> 4)) + +static stbi_uc *stbi__resample_row_hv_2(stbi_uc *out, stbi_uc *in_near, stbi_uc *in_far, int w, int hs) +{ + // need to generate 2x2 samples for every one in input + int i,t0,t1; + if (w == 1) { + out[0] = out[1] = stbi__div4(3*in_near[0] + in_far[0] + 2); + return out; + } + + t1 = 3*in_near[0] + in_far[0]; + out[0] = stbi__div4(t1+2); + for (i=1; i < w; ++i) { + t0 = t1; + t1 = 3*in_near[i]+in_far[i]; + out[i*2-1] = stbi__div16(3*t0 + t1 + 8); + out[i*2 ] = stbi__div16(3*t1 + t0 + 8); + } + out[w*2-1] = stbi__div4(t1+2); + + STBI_NOTUSED(hs); + + return out; +} + +#if defined(STBI_SSE2) || defined(STBI_NEON) +static stbi_uc *stbi__resample_row_hv_2_simd(stbi_uc *out, stbi_uc *in_near, stbi_uc *in_far, int w, int hs) +{ + // need to generate 2x2 samples for every one in input + int i=0,t0,t1; + + if (w == 1) { + out[0] = out[1] = stbi__div4(3*in_near[0] + in_far[0] + 2); + return out; + } + + t1 = 3*in_near[0] + in_far[0]; + // process groups of 8 pixels for as long as we can. + // note we can't handle the last pixel in a row in this loop + // because we need to handle the filter boundary conditions. + for (; i < ((w-1) & ~7); i += 8) { +#if defined(STBI_SSE2) + // load and perform the vertical filtering pass + // this uses 3*x + y = 4*x + (y - x) + __m128i zero = _mm_setzero_si128(); + __m128i farb = _mm_loadl_epi64((__m128i *) (in_far + i)); + __m128i nearb = _mm_loadl_epi64((__m128i *) (in_near + i)); + __m128i farw = _mm_unpacklo_epi8(farb, zero); + __m128i nearw = _mm_unpacklo_epi8(nearb, zero); + __m128i diff = _mm_sub_epi16(farw, nearw); + __m128i nears = _mm_slli_epi16(nearw, 2); + __m128i curr = _mm_add_epi16(nears, diff); // current row + + // horizontal filter works the same based on shifted vers of current + // row. "prev" is current row shifted right by 1 pixel; we need to + // insert the previous pixel value (from t1). + // "next" is current row shifted left by 1 pixel, with first pixel + // of next block of 8 pixels added in. + __m128i prv0 = _mm_slli_si128(curr, 2); + __m128i nxt0 = _mm_srli_si128(curr, 2); + __m128i prev = _mm_insert_epi16(prv0, t1, 0); + __m128i next = _mm_insert_epi16(nxt0, 3*in_near[i+8] + in_far[i+8], 7); + + // horizontal filter, polyphase implementation since it's convenient: + // even pixels = 3*cur + prev = cur*4 + (prev - cur) + // odd pixels = 3*cur + next = cur*4 + (next - cur) + // note the shared term. + __m128i bias = _mm_set1_epi16(8); + __m128i curs = _mm_slli_epi16(curr, 2); + __m128i prvd = _mm_sub_epi16(prev, curr); + __m128i nxtd = _mm_sub_epi16(next, curr); + __m128i curb = _mm_add_epi16(curs, bias); + __m128i even = _mm_add_epi16(prvd, curb); + __m128i odd = _mm_add_epi16(nxtd, curb); + + // interleave even and odd pixels, then undo scaling. + __m128i int0 = _mm_unpacklo_epi16(even, odd); + __m128i int1 = _mm_unpackhi_epi16(even, odd); + __m128i de0 = _mm_srli_epi16(int0, 4); + __m128i de1 = _mm_srli_epi16(int1, 4); + + // pack and write output + __m128i outv = _mm_packus_epi16(de0, de1); + _mm_storeu_si128((__m128i *) (out + i*2), outv); +#elif defined(STBI_NEON) + // load and perform the vertical filtering pass + // this uses 3*x + y = 4*x + (y - x) + uint8x8_t farb = vld1_u8(in_far + i); + uint8x8_t nearb = vld1_u8(in_near + i); + int16x8_t diff = vreinterpretq_s16_u16(vsubl_u8(farb, nearb)); + int16x8_t nears = vreinterpretq_s16_u16(vshll_n_u8(nearb, 2)); + int16x8_t curr = vaddq_s16(nears, diff); // current row + + // horizontal filter works the same based on shifted vers of current + // row. "prev" is current row shifted right by 1 pixel; we need to + // insert the previous pixel value (from t1). + // "next" is current row shifted left by 1 pixel, with first pixel + // of next block of 8 pixels added in. + int16x8_t prv0 = vextq_s16(curr, curr, 7); + int16x8_t nxt0 = vextq_s16(curr, curr, 1); + int16x8_t prev = vsetq_lane_s16(t1, prv0, 0); + int16x8_t next = vsetq_lane_s16(3*in_near[i+8] + in_far[i+8], nxt0, 7); + + // horizontal filter, polyphase implementation since it's convenient: + // even pixels = 3*cur + prev = cur*4 + (prev - cur) + // odd pixels = 3*cur + next = cur*4 + (next - cur) + // note the shared term. + int16x8_t curs = vshlq_n_s16(curr, 2); + int16x8_t prvd = vsubq_s16(prev, curr); + int16x8_t nxtd = vsubq_s16(next, curr); + int16x8_t even = vaddq_s16(curs, prvd); + int16x8_t odd = vaddq_s16(curs, nxtd); + + // undo scaling and round, then store with even/odd phases interleaved + uint8x8x2_t o; + o.val[0] = vqrshrun_n_s16(even, 4); + o.val[1] = vqrshrun_n_s16(odd, 4); + vst2_u8(out + i*2, o); +#endif + + // "previous" value for next iter + t1 = 3*in_near[i+7] + in_far[i+7]; + } + + t0 = t1; + t1 = 3*in_near[i] + in_far[i]; + out[i*2] = stbi__div16(3*t1 + t0 + 8); + + for (++i; i < w; ++i) { + t0 = t1; + t1 = 3*in_near[i]+in_far[i]; + out[i*2-1] = stbi__div16(3*t0 + t1 + 8); + out[i*2 ] = stbi__div16(3*t1 + t0 + 8); + } + out[w*2-1] = stbi__div4(t1+2); + + STBI_NOTUSED(hs); + + return out; +} +#endif + +static stbi_uc *stbi__resample_row_generic(stbi_uc *out, stbi_uc *in_near, stbi_uc *in_far, int w, int hs) +{ + // resample with nearest-neighbor + int i,j; + STBI_NOTUSED(in_far); + for (i=0; i < w; ++i) + for (j=0; j < hs; ++j) + out[i*hs+j] = in_near[i]; + return out; +} + +// this is a reduced-precision calculation of YCbCr-to-RGB introduced +// to make sure the code produces the same results in both SIMD and scalar +#define stbi__float2fixed(x) (((int) ((x) * 4096.0f + 0.5f)) << 8) +static void stbi__YCbCr_to_RGB_row(stbi_uc *out, const stbi_uc *y, const stbi_uc *pcb, const stbi_uc *pcr, int count, int step) +{ + int i; + for (i=0; i < count; ++i) { + int y_fixed = (y[i] << 20) + (1<<19); // rounding + int r,g,b; + int cr = pcr[i] - 128; + int cb = pcb[i] - 128; + r = y_fixed + cr* stbi__float2fixed(1.40200f); + g = y_fixed + (cr*-stbi__float2fixed(0.71414f)) + ((cb*-stbi__float2fixed(0.34414f)) & 0xffff0000); + b = y_fixed + cb* stbi__float2fixed(1.77200f); + r >>= 20; + g >>= 20; + b >>= 20; + if ((unsigned) r > 255) { if (r < 0) r = 0; else r = 255; } + if ((unsigned) g > 255) { if (g < 0) g = 0; else g = 255; } + if ((unsigned) b > 255) { if (b < 0) b = 0; else b = 255; } + out[0] = (stbi_uc)r; + out[1] = (stbi_uc)g; + out[2] = (stbi_uc)b; + out[3] = 255; + out += step; + } +} + +#if defined(STBI_SSE2) || defined(STBI_NEON) +static void stbi__YCbCr_to_RGB_simd(stbi_uc *out, stbi_uc const *y, stbi_uc const *pcb, stbi_uc const *pcr, int count, int step) +{ + int i = 0; + +#ifdef STBI_SSE2 + // step == 3 is pretty ugly on the final interleave, and i'm not convinced + // it's useful in practice (you wouldn't use it for textures, for example). + // so just accelerate step == 4 case. + if (step == 4) { + // this is a fairly straightforward implementation and not super-optimized. + __m128i signflip = _mm_set1_epi8(-0x80); + __m128i cr_const0 = _mm_set1_epi16( (short) ( 1.40200f*4096.0f+0.5f)); + __m128i cr_const1 = _mm_set1_epi16( - (short) ( 0.71414f*4096.0f+0.5f)); + __m128i cb_const0 = _mm_set1_epi16( - (short) ( 0.34414f*4096.0f+0.5f)); + __m128i cb_const1 = _mm_set1_epi16( (short) ( 1.77200f*4096.0f+0.5f)); + __m128i y_bias = _mm_set1_epi8((char) (unsigned char) 128); + __m128i xw = _mm_set1_epi16(255); // alpha channel + + for (; i+7 < count; i += 8) { + // load + __m128i y_bytes = _mm_loadl_epi64((__m128i *) (y+i)); + __m128i cr_bytes = _mm_loadl_epi64((__m128i *) (pcr+i)); + __m128i cb_bytes = _mm_loadl_epi64((__m128i *) (pcb+i)); + __m128i cr_biased = _mm_xor_si128(cr_bytes, signflip); // -128 + __m128i cb_biased = _mm_xor_si128(cb_bytes, signflip); // -128 + + // unpack to short (and left-shift cr, cb by 8) + __m128i yw = _mm_unpacklo_epi8(y_bias, y_bytes); + __m128i crw = _mm_unpacklo_epi8(_mm_setzero_si128(), cr_biased); + __m128i cbw = _mm_unpacklo_epi8(_mm_setzero_si128(), cb_biased); + + // color transform + __m128i yws = _mm_srli_epi16(yw, 4); + __m128i cr0 = _mm_mulhi_epi16(cr_const0, crw); + __m128i cb0 = _mm_mulhi_epi16(cb_const0, cbw); + __m128i cb1 = _mm_mulhi_epi16(cbw, cb_const1); + __m128i cr1 = _mm_mulhi_epi16(crw, cr_const1); + __m128i rws = _mm_add_epi16(cr0, yws); + __m128i gwt = _mm_add_epi16(cb0, yws); + __m128i bws = _mm_add_epi16(yws, cb1); + __m128i gws = _mm_add_epi16(gwt, cr1); + + // descale + __m128i rw = _mm_srai_epi16(rws, 4); + __m128i bw = _mm_srai_epi16(bws, 4); + __m128i gw = _mm_srai_epi16(gws, 4); + + // back to byte, set up for transpose + __m128i brb = _mm_packus_epi16(rw, bw); + __m128i gxb = _mm_packus_epi16(gw, xw); + + // transpose to interleave channels + __m128i t0 = _mm_unpacklo_epi8(brb, gxb); + __m128i t1 = _mm_unpackhi_epi8(brb, gxb); + __m128i o0 = _mm_unpacklo_epi16(t0, t1); + __m128i o1 = _mm_unpackhi_epi16(t0, t1); + + // store + _mm_storeu_si128((__m128i *) (out + 0), o0); + _mm_storeu_si128((__m128i *) (out + 16), o1); + out += 32; + } + } +#endif + +#ifdef STBI_NEON + // in this version, step=3 support would be easy to add. but is there demand? + if (step == 4) { + // this is a fairly straightforward implementation and not super-optimized. + uint8x8_t signflip = vdup_n_u8(0x80); + int16x8_t cr_const0 = vdupq_n_s16( (short) ( 1.40200f*4096.0f+0.5f)); + int16x8_t cr_const1 = vdupq_n_s16( - (short) ( 0.71414f*4096.0f+0.5f)); + int16x8_t cb_const0 = vdupq_n_s16( - (short) ( 0.34414f*4096.0f+0.5f)); + int16x8_t cb_const1 = vdupq_n_s16( (short) ( 1.77200f*4096.0f+0.5f)); + + for (; i+7 < count; i += 8) { + // load + uint8x8_t y_bytes = vld1_u8(y + i); + uint8x8_t cr_bytes = vld1_u8(pcr + i); + uint8x8_t cb_bytes = vld1_u8(pcb + i); + int8x8_t cr_biased = vreinterpret_s8_u8(vsub_u8(cr_bytes, signflip)); + int8x8_t cb_biased = vreinterpret_s8_u8(vsub_u8(cb_bytes, signflip)); + + // expand to s16 + int16x8_t yws = vreinterpretq_s16_u16(vshll_n_u8(y_bytes, 4)); + int16x8_t crw = vshll_n_s8(cr_biased, 7); + int16x8_t cbw = vshll_n_s8(cb_biased, 7); + + // color transform + int16x8_t cr0 = vqdmulhq_s16(crw, cr_const0); + int16x8_t cb0 = vqdmulhq_s16(cbw, cb_const0); + int16x8_t cr1 = vqdmulhq_s16(crw, cr_const1); + int16x8_t cb1 = vqdmulhq_s16(cbw, cb_const1); + int16x8_t rws = vaddq_s16(yws, cr0); + int16x8_t gws = vaddq_s16(vaddq_s16(yws, cb0), cr1); + int16x8_t bws = vaddq_s16(yws, cb1); + + // undo scaling, round, convert to byte + uint8x8x4_t o; + o.val[0] = vqrshrun_n_s16(rws, 4); + o.val[1] = vqrshrun_n_s16(gws, 4); + o.val[2] = vqrshrun_n_s16(bws, 4); + o.val[3] = vdup_n_u8(255); + + // store, interleaving r/g/b/a + vst4_u8(out, o); + out += 8*4; + } + } +#endif + + for (; i < count; ++i) { + int y_fixed = (y[i] << 20) + (1<<19); // rounding + int r,g,b; + int cr = pcr[i] - 128; + int cb = pcb[i] - 128; + r = y_fixed + cr* stbi__float2fixed(1.40200f); + g = y_fixed + cr*-stbi__float2fixed(0.71414f) + ((cb*-stbi__float2fixed(0.34414f)) & 0xffff0000); + b = y_fixed + cb* stbi__float2fixed(1.77200f); + r >>= 20; + g >>= 20; + b >>= 20; + if ((unsigned) r > 255) { if (r < 0) r = 0; else r = 255; } + if ((unsigned) g > 255) { if (g < 0) g = 0; else g = 255; } + if ((unsigned) b > 255) { if (b < 0) b = 0; else b = 255; } + out[0] = (stbi_uc)r; + out[1] = (stbi_uc)g; + out[2] = (stbi_uc)b; + out[3] = 255; + out += step; + } +} +#endif + +// set up the kernels +static void stbi__setup_jpeg(stbi__jpeg *j) +{ + j->idct_block_kernel = stbi__idct_block; + j->YCbCr_to_RGB_kernel = stbi__YCbCr_to_RGB_row; + j->resample_row_hv_2_kernel = stbi__resample_row_hv_2; + +#ifdef STBI_SSE2 + if (stbi__sse2_available()) { + j->idct_block_kernel = stbi__idct_simd; + j->YCbCr_to_RGB_kernel = stbi__YCbCr_to_RGB_simd; + j->resample_row_hv_2_kernel = stbi__resample_row_hv_2_simd; + } +#endif + +#ifdef STBI_NEON + j->idct_block_kernel = stbi__idct_simd; + j->YCbCr_to_RGB_kernel = stbi__YCbCr_to_RGB_simd; + j->resample_row_hv_2_kernel = stbi__resample_row_hv_2_simd; +#endif +} + +// clean up the temporary component buffers +static void stbi__cleanup_jpeg(stbi__jpeg *j) +{ + stbi__free_jpeg_components(j, j->s->img_n, 0); +} + +typedef struct +{ + resample_row_func resample; + stbi_uc *line0,*line1; + int hs,vs; // expansion factor in each axis + int w_lores; // horizontal pixels pre-expansion + int ystep; // how far through vertical expansion we are + int ypos; // which pre-expansion row we're on +} stbi__resample; + +// fast 0..255 * 0..255 => 0..255 rounded multiplication +static stbi_uc stbi__blinn_8x8(stbi_uc x, stbi_uc y) +{ + unsigned int t = x*y + 128; + return (stbi_uc) ((t + (t >>8)) >> 8); +} + +static stbi_uc *load_jpeg_image(stbi__jpeg *z, int *out_x, int *out_y, int *comp, int req_comp) +{ + int n, decode_n, is_rgb; + z->s->img_n = 0; // make stbi__cleanup_jpeg safe + + // validate req_comp + if (req_comp < 0 || req_comp > 4) return stbi__errpuc("bad req_comp", "Internal error"); + + // load a jpeg image from whichever source, but leave in YCbCr format + if (!stbi__decode_jpeg_image(z)) { stbi__cleanup_jpeg(z); return NULL; } + + // determine actual number of components to generate + n = req_comp ? req_comp : z->s->img_n >= 3 ? 3 : 1; + + is_rgb = z->s->img_n == 3 && (z->rgb == 3 || (z->app14_color_transform == 0 && !z->jfif)); + + if (z->s->img_n == 3 && n < 3 && !is_rgb) + decode_n = 1; + else + decode_n = z->s->img_n; + + // nothing to do if no components requested; check this now to avoid + // accessing uninitialized coutput[0] later + if (decode_n <= 0) { stbi__cleanup_jpeg(z); return NULL; } + + // resample and color-convert + { + int k; + unsigned int i,j; + stbi_uc *output; + stbi_uc *coutput[4] = { NULL, NULL, NULL, NULL }; + + stbi__resample res_comp[4]; + + for (k=0; k < decode_n; ++k) { + stbi__resample *r = &res_comp[k]; + + // allocate line buffer big enough for upsampling off the edges + // with upsample factor of 4 + z->img_comp[k].linebuf = (stbi_uc *) stbi__malloc(z->s->img_x + 3); + if (!z->img_comp[k].linebuf) { stbi__cleanup_jpeg(z); return stbi__errpuc("outofmem", "Out of memory"); } + + r->hs = z->img_h_max / z->img_comp[k].h; + r->vs = z->img_v_max / z->img_comp[k].v; + r->ystep = r->vs >> 1; + r->w_lores = (z->s->img_x + r->hs-1) / r->hs; + r->ypos = 0; + r->line0 = r->line1 = z->img_comp[k].data; + + if (r->hs == 1 && r->vs == 1) r->resample = resample_row_1; + else if (r->hs == 1 && r->vs == 2) r->resample = stbi__resample_row_v_2; + else if (r->hs == 2 && r->vs == 1) r->resample = stbi__resample_row_h_2; + else if (r->hs == 2 && r->vs == 2) r->resample = z->resample_row_hv_2_kernel; + else r->resample = stbi__resample_row_generic; + } + + // can't error after this so, this is safe + output = (stbi_uc *) stbi__malloc_mad3(n, z->s->img_x, z->s->img_y, 1); + if (!output) { stbi__cleanup_jpeg(z); return stbi__errpuc("outofmem", "Out of memory"); } + + // now go ahead and resample + for (j=0; j < z->s->img_y; ++j) { + stbi_uc *out = output + n * z->s->img_x * j; + for (k=0; k < decode_n; ++k) { + stbi__resample *r = &res_comp[k]; + int y_bot = r->ystep >= (r->vs >> 1); + coutput[k] = r->resample(z->img_comp[k].linebuf, + y_bot ? r->line1 : r->line0, + y_bot ? r->line0 : r->line1, + r->w_lores, r->hs); + if (++r->ystep >= r->vs) { + r->ystep = 0; + r->line0 = r->line1; + if (++r->ypos < z->img_comp[k].y) + r->line1 += z->img_comp[k].w2; + } + } + if (n >= 3) { + stbi_uc *y = coutput[0]; + if (z->s->img_n == 3) { + if (is_rgb) { + for (i=0; i < z->s->img_x; ++i) { + out[0] = y[i]; + out[1] = coutput[1][i]; + out[2] = coutput[2][i]; + out[3] = 255; + out += n; + } + } else { + z->YCbCr_to_RGB_kernel(out, y, coutput[1], coutput[2], z->s->img_x, n); + } + } else if (z->s->img_n == 4) { + if (z->app14_color_transform == 0) { // CMYK + for (i=0; i < z->s->img_x; ++i) { + stbi_uc m = coutput[3][i]; + out[0] = stbi__blinn_8x8(coutput[0][i], m); + out[1] = stbi__blinn_8x8(coutput[1][i], m); + out[2] = stbi__blinn_8x8(coutput[2][i], m); + out[3] = 255; + out += n; + } + } else if (z->app14_color_transform == 2) { // YCCK + z->YCbCr_to_RGB_kernel(out, y, coutput[1], coutput[2], z->s->img_x, n); + for (i=0; i < z->s->img_x; ++i) { + stbi_uc m = coutput[3][i]; + out[0] = stbi__blinn_8x8(255 - out[0], m); + out[1] = stbi__blinn_8x8(255 - out[1], m); + out[2] = stbi__blinn_8x8(255 - out[2], m); + out += n; + } + } else { // YCbCr + alpha? Ignore the fourth channel for now + z->YCbCr_to_RGB_kernel(out, y, coutput[1], coutput[2], z->s->img_x, n); + } + } else + for (i=0; i < z->s->img_x; ++i) { + out[0] = out[1] = out[2] = y[i]; + out[3] = 255; // not used if n==3 + out += n; + } + } else { + if (is_rgb) { + if (n == 1) + for (i=0; i < z->s->img_x; ++i) + *out++ = stbi__compute_y(coutput[0][i], coutput[1][i], coutput[2][i]); + else { + for (i=0; i < z->s->img_x; ++i, out += 2) { + out[0] = stbi__compute_y(coutput[0][i], coutput[1][i], coutput[2][i]); + out[1] = 255; + } + } + } else if (z->s->img_n == 4 && z->app14_color_transform == 0) { + for (i=0; i < z->s->img_x; ++i) { + stbi_uc m = coutput[3][i]; + stbi_uc r = stbi__blinn_8x8(coutput[0][i], m); + stbi_uc g = stbi__blinn_8x8(coutput[1][i], m); + stbi_uc b = stbi__blinn_8x8(coutput[2][i], m); + out[0] = stbi__compute_y(r, g, b); + out[1] = 255; + out += n; + } + } else if (z->s->img_n == 4 && z->app14_color_transform == 2) { + for (i=0; i < z->s->img_x; ++i) { + out[0] = stbi__blinn_8x8(255 - coutput[0][i], coutput[3][i]); + out[1] = 255; + out += n; + } + } else { + stbi_uc *y = coutput[0]; + if (n == 1) + for (i=0; i < z->s->img_x; ++i) out[i] = y[i]; + else + for (i=0; i < z->s->img_x; ++i) { *out++ = y[i]; *out++ = 255; } + } + } + } + stbi__cleanup_jpeg(z); + *out_x = z->s->img_x; + *out_y = z->s->img_y; + if (comp) *comp = z->s->img_n >= 3 ? 3 : 1; // report original components, not output + return output; + } +} + +static void *stbi__jpeg_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri) +{ + unsigned char* result; + stbi__jpeg* j = (stbi__jpeg*) stbi__malloc(sizeof(stbi__jpeg)); + if (!j) return stbi__errpuc("outofmem", "Out of memory"); + STBI_NOTUSED(ri); + j->s = s; + stbi__setup_jpeg(j); + result = load_jpeg_image(j, x,y,comp,req_comp); + STBI_FREE(j); + return result; +} + +static int stbi__jpeg_test(stbi__context *s) +{ + int r; + stbi__jpeg* j = (stbi__jpeg*)stbi__malloc(sizeof(stbi__jpeg)); + if (!j) return stbi__err("outofmem", "Out of memory"); + j->s = s; + stbi__setup_jpeg(j); + r = stbi__decode_jpeg_header(j, STBI__SCAN_type); + stbi__rewind(s); + STBI_FREE(j); + return r; +} + +static int stbi__jpeg_info_raw(stbi__jpeg *j, int *x, int *y, int *comp) +{ + if (!stbi__decode_jpeg_header(j, STBI__SCAN_header)) { + stbi__rewind( j->s ); + return 0; + } + if (x) *x = j->s->img_x; + if (y) *y = j->s->img_y; + if (comp) *comp = j->s->img_n >= 3 ? 3 : 1; + return 1; +} + +static int stbi__jpeg_info(stbi__context *s, int *x, int *y, int *comp) +{ + int result; + stbi__jpeg* j = (stbi__jpeg*) (stbi__malloc(sizeof(stbi__jpeg))); + if (!j) return stbi__err("outofmem", "Out of memory"); + j->s = s; + result = stbi__jpeg_info_raw(j, x, y, comp); + STBI_FREE(j); + return result; +} +#endif + +// public domain zlib decode v0.2 Sean Barrett 2006-11-18 +// simple implementation +// - all input must be provided in an upfront buffer +// - all output is written to a single output buffer (can malloc/realloc) +// performance +// - fast huffman + +#ifndef STBI_NO_ZLIB + +// fast-way is faster to check than jpeg huffman, but slow way is slower +#define STBI__ZFAST_BITS 9 // accelerate all cases in default tables +#define STBI__ZFAST_MASK ((1 << STBI__ZFAST_BITS) - 1) +#define STBI__ZNSYMS 288 // number of symbols in literal/length alphabet + +// zlib-style huffman encoding +// (jpegs packs from left, zlib from right, so can't share code) +typedef struct +{ + stbi__uint16 fast[1 << STBI__ZFAST_BITS]; + stbi__uint16 firstcode[16]; + int maxcode[17]; + stbi__uint16 firstsymbol[16]; + stbi_uc size[STBI__ZNSYMS]; + stbi__uint16 value[STBI__ZNSYMS]; +} stbi__zhuffman; + +stbi_inline static int stbi__bitreverse16(int n) +{ + n = ((n & 0xAAAA) >> 1) | ((n & 0x5555) << 1); + n = ((n & 0xCCCC) >> 2) | ((n & 0x3333) << 2); + n = ((n & 0xF0F0) >> 4) | ((n & 0x0F0F) << 4); + n = ((n & 0xFF00) >> 8) | ((n & 0x00FF) << 8); + return n; +} + +stbi_inline static int stbi__bit_reverse(int v, int bits) +{ + STBI_ASSERT(bits <= 16); + // to bit reverse n bits, reverse 16 and shift + // e.g. 11 bits, bit reverse and shift away 5 + return stbi__bitreverse16(v) >> (16-bits); +} + +static int stbi__zbuild_huffman(stbi__zhuffman *z, const stbi_uc *sizelist, int num) +{ + int i,k=0; + int code, next_code[16], sizes[17]; + + // DEFLATE spec for generating codes + memset(sizes, 0, sizeof(sizes)); + memset(z->fast, 0, sizeof(z->fast)); + for (i=0; i < num; ++i) + ++sizes[sizelist[i]]; + sizes[0] = 0; + for (i=1; i < 16; ++i) + if (sizes[i] > (1 << i)) + return stbi__err("bad sizes", "Corrupt PNG"); + code = 0; + for (i=1; i < 16; ++i) { + next_code[i] = code; + z->firstcode[i] = (stbi__uint16) code; + z->firstsymbol[i] = (stbi__uint16) k; + code = (code + sizes[i]); + if (sizes[i]) + if (code-1 >= (1 << i)) return stbi__err("bad codelengths","Corrupt PNG"); + z->maxcode[i] = code << (16-i); // preshift for inner loop + code <<= 1; + k += sizes[i]; + } + z->maxcode[16] = 0x10000; // sentinel + for (i=0; i < num; ++i) { + int s = sizelist[i]; + if (s) { + int c = next_code[s] - z->firstcode[s] + z->firstsymbol[s]; + stbi__uint16 fastv = (stbi__uint16) ((s << 9) | i); + z->size [c] = (stbi_uc ) s; + z->value[c] = (stbi__uint16) i; + if (s <= STBI__ZFAST_BITS) { + int j = stbi__bit_reverse(next_code[s],s); + while (j < (1 << STBI__ZFAST_BITS)) { + z->fast[j] = fastv; + j += (1 << s); + } + } + ++next_code[s]; + } + } + return 1; +} + +// zlib-from-memory implementation for PNG reading +// because PNG allows splitting the zlib stream arbitrarily, +// and it's annoying structurally to have PNG call ZLIB call PNG, +// we require PNG read all the IDATs and combine them into a single +// memory buffer + +typedef struct +{ + stbi_uc *zbuffer, *zbuffer_end; + int num_bits; + stbi__uint32 code_buffer; + + char *zout; + char *zout_start; + char *zout_end; + int z_expandable; + + stbi__zhuffman z_length, z_distance; +} stbi__zbuf; + +stbi_inline static int stbi__zeof(stbi__zbuf *z) +{ + return (z->zbuffer >= z->zbuffer_end); +} + +stbi_inline static stbi_uc stbi__zget8(stbi__zbuf *z) +{ + return stbi__zeof(z) ? 0 : *z->zbuffer++; +} + +static void stbi__fill_bits(stbi__zbuf *z) +{ + do { + if (z->code_buffer >= (1U << z->num_bits)) { + z->zbuffer = z->zbuffer_end; /* treat this as EOF so we fail. */ + return; + } + z->code_buffer |= (unsigned int) stbi__zget8(z) << z->num_bits; + z->num_bits += 8; + } while (z->num_bits <= 24); +} + +stbi_inline static unsigned int stbi__zreceive(stbi__zbuf *z, int n) +{ + unsigned int k; + if (z->num_bits < n) stbi__fill_bits(z); + k = z->code_buffer & ((1 << n) - 1); + z->code_buffer >>= n; + z->num_bits -= n; + return k; +} + +static int stbi__zhuffman_decode_slowpath(stbi__zbuf *a, stbi__zhuffman *z) +{ + int b,s,k; + // not resolved by fast table, so compute it the slow way + // use jpeg approach, which requires MSbits at top + k = stbi__bit_reverse(a->code_buffer, 16); + for (s=STBI__ZFAST_BITS+1; ; ++s) + if (k < z->maxcode[s]) + break; + if (s >= 16) return -1; // invalid code! + // code size is s, so: + b = (k >> (16-s)) - z->firstcode[s] + z->firstsymbol[s]; + if (b >= STBI__ZNSYMS) return -1; // some data was corrupt somewhere! + if (z->size[b] != s) return -1; // was originally an assert, but report failure instead. + a->code_buffer >>= s; + a->num_bits -= s; + return z->value[b]; +} + +stbi_inline static int stbi__zhuffman_decode(stbi__zbuf *a, stbi__zhuffman *z) +{ + int b,s; + if (a->num_bits < 16) { + if (stbi__zeof(a)) { + return -1; /* report error for unexpected end of data. */ + } + stbi__fill_bits(a); + } + b = z->fast[a->code_buffer & STBI__ZFAST_MASK]; + if (b) { + s = b >> 9; + a->code_buffer >>= s; + a->num_bits -= s; + return b & 511; + } + return stbi__zhuffman_decode_slowpath(a, z); +} + +static int stbi__zexpand(stbi__zbuf *z, char *zout, int n) // need to make room for n bytes +{ + char *q; + unsigned int cur, limit, old_limit; + z->zout = zout; + if (!z->z_expandable) return stbi__err("output buffer limit","Corrupt PNG"); + cur = (unsigned int) (z->zout - z->zout_start); + limit = old_limit = (unsigned) (z->zout_end - z->zout_start); + if (UINT_MAX - cur < (unsigned) n) return stbi__err("outofmem", "Out of memory"); + while (cur + n > limit) { + if(limit > UINT_MAX / 2) return stbi__err("outofmem", "Out of memory"); + limit *= 2; + } + q = (char *) STBI_REALLOC_SIZED(z->zout_start, old_limit, limit); + STBI_NOTUSED(old_limit); + if (q == NULL) return stbi__err("outofmem", "Out of memory"); + z->zout_start = q; + z->zout = q + cur; + z->zout_end = q + limit; + return 1; +} + +static const int stbi__zlength_base[31] = { + 3,4,5,6,7,8,9,10,11,13, + 15,17,19,23,27,31,35,43,51,59, + 67,83,99,115,131,163,195,227,258,0,0 }; + +static const int stbi__zlength_extra[31]= +{ 0,0,0,0,0,0,0,0,1,1,1,1,2,2,2,2,3,3,3,3,4,4,4,4,5,5,5,5,0,0,0 }; + +static const int stbi__zdist_base[32] = { 1,2,3,4,5,7,9,13,17,25,33,49,65,97,129,193, +257,385,513,769,1025,1537,2049,3073,4097,6145,8193,12289,16385,24577,0,0}; + +static const int stbi__zdist_extra[32] = +{ 0,0,0,0,1,1,2,2,3,3,4,4,5,5,6,6,7,7,8,8,9,9,10,10,11,11,12,12,13,13}; + +static int stbi__parse_huffman_block(stbi__zbuf *a) +{ + char *zout = a->zout; + for(;;) { + int z = stbi__zhuffman_decode(a, &a->z_length); + if (z < 256) { + if (z < 0) return stbi__err("bad huffman code","Corrupt PNG"); // error in huffman codes + if (zout >= a->zout_end) { + if (!stbi__zexpand(a, zout, 1)) return 0; + zout = a->zout; + } + *zout++ = (char) z; + } else { + stbi_uc *p; + int len,dist; + if (z == 256) { + a->zout = zout; + return 1; + } + z -= 257; + len = stbi__zlength_base[z]; + if (stbi__zlength_extra[z]) len += stbi__zreceive(a, stbi__zlength_extra[z]); + z = stbi__zhuffman_decode(a, &a->z_distance); + if (z < 0) return stbi__err("bad huffman code","Corrupt PNG"); + dist = stbi__zdist_base[z]; + if (stbi__zdist_extra[z]) dist += stbi__zreceive(a, stbi__zdist_extra[z]); + if (zout - a->zout_start < dist) return stbi__err("bad dist","Corrupt PNG"); + if (zout + len > a->zout_end) { + if (!stbi__zexpand(a, zout, len)) return 0; + zout = a->zout; + } + p = (stbi_uc *) (zout - dist); + if (dist == 1) { // run of one byte; common in images. + stbi_uc v = *p; + if (len) { do *zout++ = v; while (--len); } + } else { + if (len) { do *zout++ = *p++; while (--len); } + } + } + } +} + +static int stbi__compute_huffman_codes(stbi__zbuf *a) +{ + static const stbi_uc length_dezigzag[19] = { 16,17,18,0,8,7,9,6,10,5,11,4,12,3,13,2,14,1,15 }; + stbi__zhuffman z_codelength; + stbi_uc lencodes[286+32+137];//padding for maximum single op + stbi_uc codelength_sizes[19]; + int i,n; + + int hlit = stbi__zreceive(a,5) + 257; + int hdist = stbi__zreceive(a,5) + 1; + int hclen = stbi__zreceive(a,4) + 4; + int ntot = hlit + hdist; + + memset(codelength_sizes, 0, sizeof(codelength_sizes)); + for (i=0; i < hclen; ++i) { + int s = stbi__zreceive(a,3); + codelength_sizes[length_dezigzag[i]] = (stbi_uc) s; + } + if (!stbi__zbuild_huffman(&z_codelength, codelength_sizes, 19)) return 0; + + n = 0; + while (n < ntot) { + int c = stbi__zhuffman_decode(a, &z_codelength); + if (c < 0 || c >= 19) return stbi__err("bad codelengths", "Corrupt PNG"); + if (c < 16) + lencodes[n++] = (stbi_uc) c; + else { + stbi_uc fill = 0; + if (c == 16) { + c = stbi__zreceive(a,2)+3; + if (n == 0) return stbi__err("bad codelengths", "Corrupt PNG"); + fill = lencodes[n-1]; + } else if (c == 17) { + c = stbi__zreceive(a,3)+3; + } else if (c == 18) { + c = stbi__zreceive(a,7)+11; + } else { + return stbi__err("bad codelengths", "Corrupt PNG"); + } + if (ntot - n < c) return stbi__err("bad codelengths", "Corrupt PNG"); + memset(lencodes+n, fill, c); + n += c; + } + } + if (n != ntot) return stbi__err("bad codelengths","Corrupt PNG"); + if (!stbi__zbuild_huffman(&a->z_length, lencodes, hlit)) return 0; + if (!stbi__zbuild_huffman(&a->z_distance, lencodes+hlit, hdist)) return 0; + return 1; +} + +static int stbi__parse_uncompressed_block(stbi__zbuf *a) +{ + stbi_uc header[4]; + int len,nlen,k; + if (a->num_bits & 7) + stbi__zreceive(a, a->num_bits & 7); // discard + // drain the bit-packed data into header + k = 0; + while (a->num_bits > 0) { + header[k++] = (stbi_uc) (a->code_buffer & 255); // suppress MSVC run-time check + a->code_buffer >>= 8; + a->num_bits -= 8; + } + if (a->num_bits < 0) return stbi__err("zlib corrupt","Corrupt PNG"); + // now fill header the normal way + while (k < 4) + header[k++] = stbi__zget8(a); + len = header[1] * 256 + header[0]; + nlen = header[3] * 256 + header[2]; + if (nlen != (len ^ 0xffff)) return stbi__err("zlib corrupt","Corrupt PNG"); + if (a->zbuffer + len > a->zbuffer_end) return stbi__err("read past buffer","Corrupt PNG"); + if (a->zout + len > a->zout_end) + if (!stbi__zexpand(a, a->zout, len)) return 0; + memcpy(a->zout, a->zbuffer, len); + a->zbuffer += len; + a->zout += len; + return 1; +} + +static int stbi__parse_zlib_header(stbi__zbuf *a) +{ + int cmf = stbi__zget8(a); + int cm = cmf & 15; + /* int cinfo = cmf >> 4; */ + int flg = stbi__zget8(a); + if (stbi__zeof(a)) return stbi__err("bad zlib header","Corrupt PNG"); // zlib spec + if ((cmf*256+flg) % 31 != 0) return stbi__err("bad zlib header","Corrupt PNG"); // zlib spec + if (flg & 32) return stbi__err("no preset dict","Corrupt PNG"); // preset dictionary not allowed in png + if (cm != 8) return stbi__err("bad compression","Corrupt PNG"); // DEFLATE required for png + // window = 1 << (8 + cinfo)... but who cares, we fully buffer output + return 1; +} + +static const stbi_uc stbi__zdefault_length[STBI__ZNSYMS] = +{ + 8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8, 8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8, + 8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8, 8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8, + 8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8, 8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8, + 8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8, 8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8, + 8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8, 9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9, + 9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9, 9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9, + 9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9, 9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9, + 9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9, 9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9, + 7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7, 7,7,7,7,7,7,7,7,8,8,8,8,8,8,8,8 +}; +static const stbi_uc stbi__zdefault_distance[32] = +{ + 5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5 +}; +/* +Init algorithm: +{ + int i; // use <= to match clearly with spec + for (i=0; i <= 143; ++i) stbi__zdefault_length[i] = 8; + for ( ; i <= 255; ++i) stbi__zdefault_length[i] = 9; + for ( ; i <= 279; ++i) stbi__zdefault_length[i] = 7; + for ( ; i <= 287; ++i) stbi__zdefault_length[i] = 8; + + for (i=0; i <= 31; ++i) stbi__zdefault_distance[i] = 5; +} +*/ + +static int stbi__parse_zlib(stbi__zbuf *a, int parse_header) +{ + int final, type; + if (parse_header) + if (!stbi__parse_zlib_header(a)) return 0; + a->num_bits = 0; + a->code_buffer = 0; + do { + final = stbi__zreceive(a,1); + type = stbi__zreceive(a,2); + if (type == 0) { + if (!stbi__parse_uncompressed_block(a)) return 0; + } else if (type == 3) { + return 0; + } else { + if (type == 1) { + // use fixed code lengths + if (!stbi__zbuild_huffman(&a->z_length , stbi__zdefault_length , STBI__ZNSYMS)) return 0; + if (!stbi__zbuild_huffman(&a->z_distance, stbi__zdefault_distance, 32)) return 0; + } else { + if (!stbi__compute_huffman_codes(a)) return 0; + } + if (!stbi__parse_huffman_block(a)) return 0; + } + } while (!final); + return 1; +} + +static int stbi__do_zlib(stbi__zbuf *a, char *obuf, int olen, int exp, int parse_header) +{ + a->zout_start = obuf; + a->zout = obuf; + a->zout_end = obuf + olen; + a->z_expandable = exp; + + return stbi__parse_zlib(a, parse_header); +} + +STBIDEF char *stbi_zlib_decode_malloc_guesssize(const char *buffer, int len, int initial_size, int *outlen) +{ + stbi__zbuf a; + char *p = (char *) stbi__malloc(initial_size); + if (p == NULL) return NULL; + a.zbuffer = (stbi_uc *) buffer; + a.zbuffer_end = (stbi_uc *) buffer + len; + if (stbi__do_zlib(&a, p, initial_size, 1, 1)) { + if (outlen) *outlen = (int) (a.zout - a.zout_start); + return a.zout_start; + } else { + STBI_FREE(a.zout_start); + return NULL; + } +} + +STBIDEF char *stbi_zlib_decode_malloc(char const *buffer, int len, int *outlen) +{ + return stbi_zlib_decode_malloc_guesssize(buffer, len, 16384, outlen); +} + +STBIDEF char *stbi_zlib_decode_malloc_guesssize_headerflag(const char *buffer, int len, int initial_size, int *outlen, int parse_header) +{ + stbi__zbuf a; + char *p = (char *) stbi__malloc(initial_size); + if (p == NULL) return NULL; + a.zbuffer = (stbi_uc *) buffer; + a.zbuffer_end = (stbi_uc *) buffer + len; + if (stbi__do_zlib(&a, p, initial_size, 1, parse_header)) { + if (outlen) *outlen = (int) (a.zout - a.zout_start); + return a.zout_start; + } else { + STBI_FREE(a.zout_start); + return NULL; + } +} + +STBIDEF int stbi_zlib_decode_buffer(char *obuffer, int olen, char const *ibuffer, int ilen) +{ + stbi__zbuf a; + a.zbuffer = (stbi_uc *) ibuffer; + a.zbuffer_end = (stbi_uc *) ibuffer + ilen; + if (stbi__do_zlib(&a, obuffer, olen, 0, 1)) + return (int) (a.zout - a.zout_start); + else + return -1; +} + +STBIDEF char *stbi_zlib_decode_noheader_malloc(char const *buffer, int len, int *outlen) +{ + stbi__zbuf a; + char *p = (char *) stbi__malloc(16384); + if (p == NULL) return NULL; + a.zbuffer = (stbi_uc *) buffer; + a.zbuffer_end = (stbi_uc *) buffer+len; + if (stbi__do_zlib(&a, p, 16384, 1, 0)) { + if (outlen) *outlen = (int) (a.zout - a.zout_start); + return a.zout_start; + } else { + STBI_FREE(a.zout_start); + return NULL; + } +} + +STBIDEF int stbi_zlib_decode_noheader_buffer(char *obuffer, int olen, const char *ibuffer, int ilen) +{ + stbi__zbuf a; + a.zbuffer = (stbi_uc *) ibuffer; + a.zbuffer_end = (stbi_uc *) ibuffer + ilen; + if (stbi__do_zlib(&a, obuffer, olen, 0, 0)) + return (int) (a.zout - a.zout_start); + else + return -1; +} +#endif + +// public domain "baseline" PNG decoder v0.10 Sean Barrett 2006-11-18 +// simple implementation +// - only 8-bit samples +// - no CRC checking +// - allocates lots of intermediate memory +// - avoids problem of streaming data between subsystems +// - avoids explicit window management +// performance +// - uses stb_zlib, a PD zlib implementation with fast huffman decoding + +#ifndef STBI_NO_PNG +typedef struct +{ + stbi__uint32 length; + stbi__uint32 type; +} stbi__pngchunk; + +static stbi__pngchunk stbi__get_chunk_header(stbi__context *s) +{ + stbi__pngchunk c; + c.length = stbi__get32be(s); + c.type = stbi__get32be(s); + return c; +} + +static int stbi__check_png_header(stbi__context *s) +{ + static const stbi_uc png_sig[8] = { 137,80,78,71,13,10,26,10 }; + int i; + for (i=0; i < 8; ++i) + if (stbi__get8(s) != png_sig[i]) return stbi__err("bad png sig","Not a PNG"); + return 1; +} + +typedef struct +{ + stbi__context *s; + stbi_uc *idata, *expanded, *out; + int depth; +} stbi__png; + + +enum { + STBI__F_none=0, + STBI__F_sub=1, + STBI__F_up=2, + STBI__F_avg=3, + STBI__F_paeth=4, + // synthetic filters used for first scanline to avoid needing a dummy row of 0s + STBI__F_avg_first, + STBI__F_paeth_first +}; + +static stbi_uc first_row_filter[5] = +{ + STBI__F_none, + STBI__F_sub, + STBI__F_none, + STBI__F_avg_first, + STBI__F_paeth_first +}; + +static int stbi__paeth(int a, int b, int c) +{ + int p = a + b - c; + int pa = abs(p-a); + int pb = abs(p-b); + int pc = abs(p-c); + if (pa <= pb && pa <= pc) return a; + if (pb <= pc) return b; + return c; +} + +static const stbi_uc stbi__depth_scale_table[9] = { 0, 0xff, 0x55, 0, 0x11, 0,0,0, 0x01 }; + +// create the png data from post-deflated data +static int stbi__create_png_image_raw(stbi__png *a, stbi_uc *raw, stbi__uint32 raw_len, int out_n, stbi__uint32 x, stbi__uint32 y, int depth, int color) +{ + int bytes = (depth == 16? 2 : 1); + stbi__context *s = a->s; + stbi__uint32 i,j,stride = x*out_n*bytes; + stbi__uint32 img_len, img_width_bytes; + int k; + int img_n = s->img_n; // copy it into a local for later + + int output_bytes = out_n*bytes; + int filter_bytes = img_n*bytes; + int width = x; + + STBI_ASSERT(out_n == s->img_n || out_n == s->img_n+1); + a->out = (stbi_uc *) stbi__malloc_mad3(x, y, output_bytes, 0); // extra bytes to write off the end into + if (!a->out) return stbi__err("outofmem", "Out of memory"); + + if (!stbi__mad3sizes_valid(img_n, x, depth, 7)) return stbi__err("too large", "Corrupt PNG"); + img_width_bytes = (((img_n * x * depth) + 7) >> 3); + img_len = (img_width_bytes + 1) * y; + + // we used to check for exact match between raw_len and img_len on non-interlaced PNGs, + // but issue #276 reported a PNG in the wild that had extra data at the end (all zeros), + // so just check for raw_len < img_len always. + if (raw_len < img_len) return stbi__err("not enough pixels","Corrupt PNG"); + + for (j=0; j < y; ++j) { + stbi_uc *cur = a->out + stride*j; + stbi_uc *prior; + int filter = *raw++; + + if (filter > 4) + return stbi__err("invalid filter","Corrupt PNG"); + + if (depth < 8) { + if (img_width_bytes > x) return stbi__err("invalid width","Corrupt PNG"); + cur += x*out_n - img_width_bytes; // store output to the rightmost img_len bytes, so we can decode in place + filter_bytes = 1; + width = img_width_bytes; + } + prior = cur - stride; // bugfix: need to compute this after 'cur +=' computation above + + // if first row, use special filter that doesn't sample previous row + if (j == 0) filter = first_row_filter[filter]; + + // handle first byte explicitly + for (k=0; k < filter_bytes; ++k) { + switch (filter) { + case STBI__F_none : cur[k] = raw[k]; break; + case STBI__F_sub : cur[k] = raw[k]; break; + case STBI__F_up : cur[k] = STBI__BYTECAST(raw[k] + prior[k]); break; + case STBI__F_avg : cur[k] = STBI__BYTECAST(raw[k] + (prior[k]>>1)); break; + case STBI__F_paeth : cur[k] = STBI__BYTECAST(raw[k] + stbi__paeth(0,prior[k],0)); break; + case STBI__F_avg_first : cur[k] = raw[k]; break; + case STBI__F_paeth_first: cur[k] = raw[k]; break; + } + } + + if (depth == 8) { + if (img_n != out_n) + cur[img_n] = 255; // first pixel + raw += img_n; + cur += out_n; + prior += out_n; + } else if (depth == 16) { + if (img_n != out_n) { + cur[filter_bytes] = 255; // first pixel top byte + cur[filter_bytes+1] = 255; // first pixel bottom byte + } + raw += filter_bytes; + cur += output_bytes; + prior += output_bytes; + } else { + raw += 1; + cur += 1; + prior += 1; + } + + // this is a little gross, so that we don't switch per-pixel or per-component + if (depth < 8 || img_n == out_n) { + int nk = (width - 1)*filter_bytes; + #define STBI__CASE(f) \ + case f: \ + for (k=0; k < nk; ++k) + switch (filter) { + // "none" filter turns into a memcpy here; make that explicit. + case STBI__F_none: memcpy(cur, raw, nk); break; + STBI__CASE(STBI__F_sub) { cur[k] = STBI__BYTECAST(raw[k] + cur[k-filter_bytes]); } break; + STBI__CASE(STBI__F_up) { cur[k] = STBI__BYTECAST(raw[k] + prior[k]); } break; + STBI__CASE(STBI__F_avg) { cur[k] = STBI__BYTECAST(raw[k] + ((prior[k] + cur[k-filter_bytes])>>1)); } break; + STBI__CASE(STBI__F_paeth) { cur[k] = STBI__BYTECAST(raw[k] + stbi__paeth(cur[k-filter_bytes],prior[k],prior[k-filter_bytes])); } break; + STBI__CASE(STBI__F_avg_first) { cur[k] = STBI__BYTECAST(raw[k] + (cur[k-filter_bytes] >> 1)); } break; + STBI__CASE(STBI__F_paeth_first) { cur[k] = STBI__BYTECAST(raw[k] + stbi__paeth(cur[k-filter_bytes],0,0)); } break; + } + #undef STBI__CASE + raw += nk; + } else { + STBI_ASSERT(img_n+1 == out_n); + #define STBI__CASE(f) \ + case f: \ + for (i=x-1; i >= 1; --i, cur[filter_bytes]=255,raw+=filter_bytes,cur+=output_bytes,prior+=output_bytes) \ + for (k=0; k < filter_bytes; ++k) + switch (filter) { + STBI__CASE(STBI__F_none) { cur[k] = raw[k]; } break; + STBI__CASE(STBI__F_sub) { cur[k] = STBI__BYTECAST(raw[k] + cur[k- output_bytes]); } break; + STBI__CASE(STBI__F_up) { cur[k] = STBI__BYTECAST(raw[k] + prior[k]); } break; + STBI__CASE(STBI__F_avg) { cur[k] = STBI__BYTECAST(raw[k] + ((prior[k] + cur[k- output_bytes])>>1)); } break; + STBI__CASE(STBI__F_paeth) { cur[k] = STBI__BYTECAST(raw[k] + stbi__paeth(cur[k- output_bytes],prior[k],prior[k- output_bytes])); } break; + STBI__CASE(STBI__F_avg_first) { cur[k] = STBI__BYTECAST(raw[k] + (cur[k- output_bytes] >> 1)); } break; + STBI__CASE(STBI__F_paeth_first) { cur[k] = STBI__BYTECAST(raw[k] + stbi__paeth(cur[k- output_bytes],0,0)); } break; + } + #undef STBI__CASE + + // the loop above sets the high byte of the pixels' alpha, but for + // 16 bit png files we also need the low byte set. we'll do that here. + if (depth == 16) { + cur = a->out + stride*j; // start at the beginning of the row again + for (i=0; i < x; ++i,cur+=output_bytes) { + cur[filter_bytes+1] = 255; + } + } + } + } + + // we make a separate pass to expand bits to pixels; for performance, + // this could run two scanlines behind the above code, so it won't + // intefere with filtering but will still be in the cache. + if (depth < 8) { + for (j=0; j < y; ++j) { + stbi_uc *cur = a->out + stride*j; + stbi_uc *in = a->out + stride*j + x*out_n - img_width_bytes; + // unpack 1/2/4-bit into a 8-bit buffer. allows us to keep the common 8-bit path optimal at minimal cost for 1/2/4-bit + // png guarante byte alignment, if width is not multiple of 8/4/2 we'll decode dummy trailing data that will be skipped in the later loop + stbi_uc scale = (color == 0) ? stbi__depth_scale_table[depth] : 1; // scale grayscale values to 0..255 range + + // note that the final byte might overshoot and write more data than desired. + // we can allocate enough data that this never writes out of memory, but it + // could also overwrite the next scanline. can it overwrite non-empty data + // on the next scanline? yes, consider 1-pixel-wide scanlines with 1-bit-per-pixel. + // so we need to explicitly clamp the final ones + + if (depth == 4) { + for (k=x*img_n; k >= 2; k-=2, ++in) { + *cur++ = scale * ((*in >> 4) ); + *cur++ = scale * ((*in ) & 0x0f); + } + if (k > 0) *cur++ = scale * ((*in >> 4) ); + } else if (depth == 2) { + for (k=x*img_n; k >= 4; k-=4, ++in) { + *cur++ = scale * ((*in >> 6) ); + *cur++ = scale * ((*in >> 4) & 0x03); + *cur++ = scale * ((*in >> 2) & 0x03); + *cur++ = scale * ((*in ) & 0x03); + } + if (k > 0) *cur++ = scale * ((*in >> 6) ); + if (k > 1) *cur++ = scale * ((*in >> 4) & 0x03); + if (k > 2) *cur++ = scale * ((*in >> 2) & 0x03); + } else if (depth == 1) { + for (k=x*img_n; k >= 8; k-=8, ++in) { + *cur++ = scale * ((*in >> 7) ); + *cur++ = scale * ((*in >> 6) & 0x01); + *cur++ = scale * ((*in >> 5) & 0x01); + *cur++ = scale * ((*in >> 4) & 0x01); + *cur++ = scale * ((*in >> 3) & 0x01); + *cur++ = scale * ((*in >> 2) & 0x01); + *cur++ = scale * ((*in >> 1) & 0x01); + *cur++ = scale * ((*in ) & 0x01); + } + if (k > 0) *cur++ = scale * ((*in >> 7) ); + if (k > 1) *cur++ = scale * ((*in >> 6) & 0x01); + if (k > 2) *cur++ = scale * ((*in >> 5) & 0x01); + if (k > 3) *cur++ = scale * ((*in >> 4) & 0x01); + if (k > 4) *cur++ = scale * ((*in >> 3) & 0x01); + if (k > 5) *cur++ = scale * ((*in >> 2) & 0x01); + if (k > 6) *cur++ = scale * ((*in >> 1) & 0x01); + } + if (img_n != out_n) { + int q; + // insert alpha = 255 + cur = a->out + stride*j; + if (img_n == 1) { + for (q=x-1; q >= 0; --q) { + cur[q*2+1] = 255; + cur[q*2+0] = cur[q]; + } + } else { + STBI_ASSERT(img_n == 3); + for (q=x-1; q >= 0; --q) { + cur[q*4+3] = 255; + cur[q*4+2] = cur[q*3+2]; + cur[q*4+1] = cur[q*3+1]; + cur[q*4+0] = cur[q*3+0]; + } + } + } + } + } else if (depth == 16) { + // force the image data from big-endian to platform-native. + // this is done in a separate pass due to the decoding relying + // on the data being untouched, but could probably be done + // per-line during decode if care is taken. + stbi_uc *cur = a->out; + stbi__uint16 *cur16 = (stbi__uint16*)cur; + + for(i=0; i < x*y*out_n; ++i,cur16++,cur+=2) { + *cur16 = (cur[0] << 8) | cur[1]; + } + } + + return 1; +} + +static int stbi__create_png_image(stbi__png *a, stbi_uc *image_data, stbi__uint32 image_data_len, int out_n, int depth, int color, int interlaced) +{ + int bytes = (depth == 16 ? 2 : 1); + int out_bytes = out_n * bytes; + stbi_uc *final; + int p; + if (!interlaced) + return stbi__create_png_image_raw(a, image_data, image_data_len, out_n, a->s->img_x, a->s->img_y, depth, color); + + // de-interlacing + final = (stbi_uc *) stbi__malloc_mad3(a->s->img_x, a->s->img_y, out_bytes, 0); + if (!final) return stbi__err("outofmem", "Out of memory"); + for (p=0; p < 7; ++p) { + int xorig[] = { 0,4,0,2,0,1,0 }; + int yorig[] = { 0,0,4,0,2,0,1 }; + int xspc[] = { 8,8,4,4,2,2,1 }; + int yspc[] = { 8,8,8,4,4,2,2 }; + int i,j,x,y; + // pass1_x[4] = 0, pass1_x[5] = 1, pass1_x[12] = 1 + x = (a->s->img_x - xorig[p] + xspc[p]-1) / xspc[p]; + y = (a->s->img_y - yorig[p] + yspc[p]-1) / yspc[p]; + if (x && y) { + stbi__uint32 img_len = ((((a->s->img_n * x * depth) + 7) >> 3) + 1) * y; + if (!stbi__create_png_image_raw(a, image_data, image_data_len, out_n, x, y, depth, color)) { + STBI_FREE(final); + return 0; + } + for (j=0; j < y; ++j) { + for (i=0; i < x; ++i) { + int out_y = j*yspc[p]+yorig[p]; + int out_x = i*xspc[p]+xorig[p]; + memcpy(final + out_y*a->s->img_x*out_bytes + out_x*out_bytes, + a->out + (j*x+i)*out_bytes, out_bytes); + } + } + STBI_FREE(a->out); + image_data += img_len; + image_data_len -= img_len; + } + } + a->out = final; + + return 1; +} + +static int stbi__compute_transparency(stbi__png *z, stbi_uc tc[3], int out_n) +{ + stbi__context *s = z->s; + stbi__uint32 i, pixel_count = s->img_x * s->img_y; + stbi_uc *p = z->out; + + // compute color-based transparency, assuming we've + // already got 255 as the alpha value in the output + STBI_ASSERT(out_n == 2 || out_n == 4); + + if (out_n == 2) { + for (i=0; i < pixel_count; ++i) { + p[1] = (p[0] == tc[0] ? 0 : 255); + p += 2; + } + } else { + for (i=0; i < pixel_count; ++i) { + if (p[0] == tc[0] && p[1] == tc[1] && p[2] == tc[2]) + p[3] = 0; + p += 4; + } + } + return 1; +} + +static int stbi__compute_transparency16(stbi__png *z, stbi__uint16 tc[3], int out_n) +{ + stbi__context *s = z->s; + stbi__uint32 i, pixel_count = s->img_x * s->img_y; + stbi__uint16 *p = (stbi__uint16*) z->out; + + // compute color-based transparency, assuming we've + // already got 65535 as the alpha value in the output + STBI_ASSERT(out_n == 2 || out_n == 4); + + if (out_n == 2) { + for (i = 0; i < pixel_count; ++i) { + p[1] = (p[0] == tc[0] ? 0 : 65535); + p += 2; + } + } else { + for (i = 0; i < pixel_count; ++i) { + if (p[0] == tc[0] && p[1] == tc[1] && p[2] == tc[2]) + p[3] = 0; + p += 4; + } + } + return 1; +} + +static int stbi__expand_png_palette(stbi__png *a, stbi_uc *palette, int len, int pal_img_n) +{ + stbi__uint32 i, pixel_count = a->s->img_x * a->s->img_y; + stbi_uc *p, *temp_out, *orig = a->out; + + p = (stbi_uc *) stbi__malloc_mad2(pixel_count, pal_img_n, 0); + if (p == NULL) return stbi__err("outofmem", "Out of memory"); + + // between here and free(out) below, exitting would leak + temp_out = p; + + if (pal_img_n == 3) { + for (i=0; i < pixel_count; ++i) { + int n = orig[i]*4; + p[0] = palette[n ]; + p[1] = palette[n+1]; + p[2] = palette[n+2]; + p += 3; + } + } else { + for (i=0; i < pixel_count; ++i) { + int n = orig[i]*4; + p[0] = palette[n ]; + p[1] = palette[n+1]; + p[2] = palette[n+2]; + p[3] = palette[n+3]; + p += 4; + } + } + STBI_FREE(a->out); + a->out = temp_out; + + STBI_NOTUSED(len); + + return 1; +} + +static int stbi__unpremultiply_on_load_global = 0; +static int stbi__de_iphone_flag_global = 0; + +STBIDEF void stbi_set_unpremultiply_on_load(int flag_true_if_should_unpremultiply) +{ + stbi__unpremultiply_on_load_global = flag_true_if_should_unpremultiply; +} + +STBIDEF void stbi_convert_iphone_png_to_rgb(int flag_true_if_should_convert) +{ + stbi__de_iphone_flag_global = flag_true_if_should_convert; +} + +#ifndef STBI_THREAD_LOCAL +#define stbi__unpremultiply_on_load stbi__unpremultiply_on_load_global +#define stbi__de_iphone_flag stbi__de_iphone_flag_global +#else +static STBI_THREAD_LOCAL int stbi__unpremultiply_on_load_local, stbi__unpremultiply_on_load_set; +static STBI_THREAD_LOCAL int stbi__de_iphone_flag_local, stbi__de_iphone_flag_set; + +STBIDEF void stbi__unpremultiply_on_load_thread(int flag_true_if_should_unpremultiply) +{ + stbi__unpremultiply_on_load_local = flag_true_if_should_unpremultiply; + stbi__unpremultiply_on_load_set = 1; +} + +STBIDEF void stbi_convert_iphone_png_to_rgb_thread(int flag_true_if_should_convert) +{ + stbi__de_iphone_flag_local = flag_true_if_should_convert; + stbi__de_iphone_flag_set = 1; +} + +#define stbi__unpremultiply_on_load (stbi__unpremultiply_on_load_set \ + ? stbi__unpremultiply_on_load_local \ + : stbi__unpremultiply_on_load_global) +#define stbi__de_iphone_flag (stbi__de_iphone_flag_set \ + ? stbi__de_iphone_flag_local \ + : stbi__de_iphone_flag_global) +#endif // STBI_THREAD_LOCAL + +static void stbi__de_iphone(stbi__png *z) +{ + stbi__context *s = z->s; + stbi__uint32 i, pixel_count = s->img_x * s->img_y; + stbi_uc *p = z->out; + + if (s->img_out_n == 3) { // convert bgr to rgb + for (i=0; i < pixel_count; ++i) { + stbi_uc t = p[0]; + p[0] = p[2]; + p[2] = t; + p += 3; + } + } else { + STBI_ASSERT(s->img_out_n == 4); + if (stbi__unpremultiply_on_load) { + // convert bgr to rgb and unpremultiply + for (i=0; i < pixel_count; ++i) { + stbi_uc a = p[3]; + stbi_uc t = p[0]; + if (a) { + stbi_uc half = a / 2; + p[0] = (p[2] * 255 + half) / a; + p[1] = (p[1] * 255 + half) / a; + p[2] = ( t * 255 + half) / a; + } else { + p[0] = p[2]; + p[2] = t; + } + p += 4; + } + } else { + // convert bgr to rgb + for (i=0; i < pixel_count; ++i) { + stbi_uc t = p[0]; + p[0] = p[2]; + p[2] = t; + p += 4; + } + } + } +} + +#define STBI__PNG_TYPE(a,b,c,d) (((unsigned) (a) << 24) + ((unsigned) (b) << 16) + ((unsigned) (c) << 8) + (unsigned) (d)) + +static int stbi__parse_png_file(stbi__png *z, int scan, int req_comp) +{ + stbi_uc palette[1024], pal_img_n=0; + stbi_uc has_trans=0, tc[3]={0}; + stbi__uint16 tc16[3]; + stbi__uint32 ioff=0, idata_limit=0, i, pal_len=0; + int first=1,k,interlace=0, color=0, is_iphone=0; + stbi__context *s = z->s; + + z->expanded = NULL; + z->idata = NULL; + z->out = NULL; + + if (!stbi__check_png_header(s)) return 0; + + if (scan == STBI__SCAN_type) return 1; + + for (;;) { + stbi__pngchunk c = stbi__get_chunk_header(s); + switch (c.type) { + case STBI__PNG_TYPE('C','g','B','I'): + is_iphone = 1; + stbi__skip(s, c.length); + break; + case STBI__PNG_TYPE('I','H','D','R'): { + int comp,filter; + if (!first) return stbi__err("multiple IHDR","Corrupt PNG"); + first = 0; + if (c.length != 13) return stbi__err("bad IHDR len","Corrupt PNG"); + s->img_x = stbi__get32be(s); + s->img_y = stbi__get32be(s); + if (s->img_y > STBI_MAX_DIMENSIONS) return stbi__err("too large","Very large image (corrupt?)"); + if (s->img_x > STBI_MAX_DIMENSIONS) return stbi__err("too large","Very large image (corrupt?)"); + z->depth = stbi__get8(s); if (z->depth != 1 && z->depth != 2 && z->depth != 4 && z->depth != 8 && z->depth != 16) return stbi__err("1/2/4/8/16-bit only","PNG not supported: 1/2/4/8/16-bit only"); + color = stbi__get8(s); if (color > 6) return stbi__err("bad ctype","Corrupt PNG"); + if (color == 3 && z->depth == 16) return stbi__err("bad ctype","Corrupt PNG"); + if (color == 3) pal_img_n = 3; else if (color & 1) return stbi__err("bad ctype","Corrupt PNG"); + comp = stbi__get8(s); if (comp) return stbi__err("bad comp method","Corrupt PNG"); + filter= stbi__get8(s); if (filter) return stbi__err("bad filter method","Corrupt PNG"); + interlace = stbi__get8(s); if (interlace>1) return stbi__err("bad interlace method","Corrupt PNG"); + if (!s->img_x || !s->img_y) return stbi__err("0-pixel image","Corrupt PNG"); + if (!pal_img_n) { + s->img_n = (color & 2 ? 3 : 1) + (color & 4 ? 1 : 0); + if ((1 << 30) / s->img_x / s->img_n < s->img_y) return stbi__err("too large", "Image too large to decode"); + if (scan == STBI__SCAN_header) return 1; + } else { + // if paletted, then pal_n is our final components, and + // img_n is # components to decompress/filter. + s->img_n = 1; + if ((1 << 30) / s->img_x / 4 < s->img_y) return stbi__err("too large","Corrupt PNG"); + // if SCAN_header, have to scan to see if we have a tRNS + } + break; + } + + case STBI__PNG_TYPE('P','L','T','E'): { + if (first) return stbi__err("first not IHDR", "Corrupt PNG"); + if (c.length > 256*3) return stbi__err("invalid PLTE","Corrupt PNG"); + pal_len = c.length / 3; + if (pal_len * 3 != c.length) return stbi__err("invalid PLTE","Corrupt PNG"); + for (i=0; i < pal_len; ++i) { + palette[i*4+0] = stbi__get8(s); + palette[i*4+1] = stbi__get8(s); + palette[i*4+2] = stbi__get8(s); + palette[i*4+3] = 255; + } + break; + } + + case STBI__PNG_TYPE('t','R','N','S'): { + if (first) return stbi__err("first not IHDR", "Corrupt PNG"); + if (z->idata) return stbi__err("tRNS after IDAT","Corrupt PNG"); + if (pal_img_n) { + if (scan == STBI__SCAN_header) { s->img_n = 4; return 1; } + if (pal_len == 0) return stbi__err("tRNS before PLTE","Corrupt PNG"); + if (c.length > pal_len) return stbi__err("bad tRNS len","Corrupt PNG"); + pal_img_n = 4; + for (i=0; i < c.length; ++i) + palette[i*4+3] = stbi__get8(s); + } else { + if (!(s->img_n & 1)) return stbi__err("tRNS with alpha","Corrupt PNG"); + if (c.length != (stbi__uint32) s->img_n*2) return stbi__err("bad tRNS len","Corrupt PNG"); + has_trans = 1; + if (z->depth == 16) { + for (k = 0; k < s->img_n; ++k) tc16[k] = (stbi__uint16)stbi__get16be(s); // copy the values as-is + } else { + for (k = 0; k < s->img_n; ++k) tc[k] = (stbi_uc)(stbi__get16be(s) & 255) * stbi__depth_scale_table[z->depth]; // non 8-bit images will be larger + } + } + break; + } + + case STBI__PNG_TYPE('I','D','A','T'): { + if (first) return stbi__err("first not IHDR", "Corrupt PNG"); + if (pal_img_n && !pal_len) return stbi__err("no PLTE","Corrupt PNG"); + if (scan == STBI__SCAN_header) { s->img_n = pal_img_n; return 1; } + if ((int)(ioff + c.length) < (int)ioff) return 0; + if (ioff + c.length > idata_limit) { + stbi__uint32 idata_limit_old = idata_limit; + stbi_uc *p; + if (idata_limit == 0) idata_limit = c.length > 4096 ? c.length : 4096; + while (ioff + c.length > idata_limit) + idata_limit *= 2; + STBI_NOTUSED(idata_limit_old); + p = (stbi_uc *) STBI_REALLOC_SIZED(z->idata, idata_limit_old, idata_limit); if (p == NULL) return stbi__err("outofmem", "Out of memory"); + z->idata = p; + } + if (!stbi__getn(s, z->idata+ioff,c.length)) return stbi__err("outofdata","Corrupt PNG"); + ioff += c.length; + break; + } + + case STBI__PNG_TYPE('I','E','N','D'): { + stbi__uint32 raw_len, bpl; + if (first) return stbi__err("first not IHDR", "Corrupt PNG"); + if (scan != STBI__SCAN_load) return 1; + if (z->idata == NULL) return stbi__err("no IDAT","Corrupt PNG"); + // initial guess for decoded data size to avoid unnecessary reallocs + bpl = (s->img_x * z->depth + 7) / 8; // bytes per line, per component + raw_len = bpl * s->img_y * s->img_n /* pixels */ + s->img_y /* filter mode per row */; + z->expanded = (stbi_uc *) stbi_zlib_decode_malloc_guesssize_headerflag((char *) z->idata, ioff, raw_len, (int *) &raw_len, !is_iphone); + if (z->expanded == NULL) return 0; // zlib should set error + STBI_FREE(z->idata); z->idata = NULL; + if ((req_comp == s->img_n+1 && req_comp != 3 && !pal_img_n) || has_trans) + s->img_out_n = s->img_n+1; + else + s->img_out_n = s->img_n; + if (!stbi__create_png_image(z, z->expanded, raw_len, s->img_out_n, z->depth, color, interlace)) return 0; + if (has_trans) { + if (z->depth == 16) { + if (!stbi__compute_transparency16(z, tc16, s->img_out_n)) return 0; + } else { + if (!stbi__compute_transparency(z, tc, s->img_out_n)) return 0; + } + } + if (is_iphone && stbi__de_iphone_flag && s->img_out_n > 2) + stbi__de_iphone(z); + if (pal_img_n) { + // pal_img_n == 3 or 4 + s->img_n = pal_img_n; // record the actual colors we had + s->img_out_n = pal_img_n; + if (req_comp >= 3) s->img_out_n = req_comp; + if (!stbi__expand_png_palette(z, palette, pal_len, s->img_out_n)) + return 0; + } else if (has_trans) { + // non-paletted image with tRNS -> source image has (constant) alpha + ++s->img_n; + } + STBI_FREE(z->expanded); z->expanded = NULL; + // end of PNG chunk, read and skip CRC + stbi__get32be(s); + return 1; + } + + default: + // if critical, fail + if (first) return stbi__err("first not IHDR", "Corrupt PNG"); + if ((c.type & (1 << 29)) == 0) { + #ifndef STBI_NO_FAILURE_STRINGS + // not threadsafe + static char invalid_chunk[] = "XXXX PNG chunk not known"; + invalid_chunk[0] = STBI__BYTECAST(c.type >> 24); + invalid_chunk[1] = STBI__BYTECAST(c.type >> 16); + invalid_chunk[2] = STBI__BYTECAST(c.type >> 8); + invalid_chunk[3] = STBI__BYTECAST(c.type >> 0); + #endif + return stbi__err(invalid_chunk, "PNG not supported: unknown PNG chunk type"); + } + stbi__skip(s, c.length); + break; + } + // end of PNG chunk, read and skip CRC + stbi__get32be(s); + } +} + +static void *stbi__do_png(stbi__png *p, int *x, int *y, int *n, int req_comp, stbi__result_info *ri) +{ + void *result=NULL; + if (req_comp < 0 || req_comp > 4) return stbi__errpuc("bad req_comp", "Internal error"); + if (stbi__parse_png_file(p, STBI__SCAN_load, req_comp)) { + if (p->depth <= 8) + ri->bits_per_channel = 8; + else if (p->depth == 16) + ri->bits_per_channel = 16; + else + return stbi__errpuc("bad bits_per_channel", "PNG not supported: unsupported color depth"); + result = p->out; + p->out = NULL; + if (req_comp && req_comp != p->s->img_out_n) { + if (ri->bits_per_channel == 8) + result = stbi__convert_format((unsigned char *) result, p->s->img_out_n, req_comp, p->s->img_x, p->s->img_y); + else + result = stbi__convert_format16((stbi__uint16 *) result, p->s->img_out_n, req_comp, p->s->img_x, p->s->img_y); + p->s->img_out_n = req_comp; + if (result == NULL) return result; + } + *x = p->s->img_x; + *y = p->s->img_y; + if (n) *n = p->s->img_n; + } + STBI_FREE(p->out); p->out = NULL; + STBI_FREE(p->expanded); p->expanded = NULL; + STBI_FREE(p->idata); p->idata = NULL; + + return result; +} + +static void *stbi__png_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri) +{ + stbi__png p; + p.s = s; + return stbi__do_png(&p, x,y,comp,req_comp, ri); +} + +static int stbi__png_test(stbi__context *s) +{ + int r; + r = stbi__check_png_header(s); + stbi__rewind(s); + return r; +} + +static int stbi__png_info_raw(stbi__png *p, int *x, int *y, int *comp) +{ + if (!stbi__parse_png_file(p, STBI__SCAN_header, 0)) { + stbi__rewind( p->s ); + return 0; + } + if (x) *x = p->s->img_x; + if (y) *y = p->s->img_y; + if (comp) *comp = p->s->img_n; + return 1; +} + +static int stbi__png_info(stbi__context *s, int *x, int *y, int *comp) +{ + stbi__png p; + p.s = s; + return stbi__png_info_raw(&p, x, y, comp); +} + +static int stbi__png_is16(stbi__context *s) +{ + stbi__png p; + p.s = s; + if (!stbi__png_info_raw(&p, NULL, NULL, NULL)) + return 0; + if (p.depth != 16) { + stbi__rewind(p.s); + return 0; + } + return 1; +} +#endif + +// Microsoft/Windows BMP image + +#ifndef STBI_NO_BMP +static int stbi__bmp_test_raw(stbi__context *s) +{ + int r; + int sz; + if (stbi__get8(s) != 'B') return 0; + if (stbi__get8(s) != 'M') return 0; + stbi__get32le(s); // discard filesize + stbi__get16le(s); // discard reserved + stbi__get16le(s); // discard reserved + stbi__get32le(s); // discard data offset + sz = stbi__get32le(s); + r = (sz == 12 || sz == 40 || sz == 56 || sz == 108 || sz == 124); + return r; +} + +static int stbi__bmp_test(stbi__context *s) +{ + int r = stbi__bmp_test_raw(s); + stbi__rewind(s); + return r; +} + + +// returns 0..31 for the highest set bit +static int stbi__high_bit(unsigned int z) +{ + int n=0; + if (z == 0) return -1; + if (z >= 0x10000) { n += 16; z >>= 16; } + if (z >= 0x00100) { n += 8; z >>= 8; } + if (z >= 0x00010) { n += 4; z >>= 4; } + if (z >= 0x00004) { n += 2; z >>= 2; } + if (z >= 0x00002) { n += 1;/* >>= 1;*/ } + return n; +} + +static int stbi__bitcount(unsigned int a) +{ + a = (a & 0x55555555) + ((a >> 1) & 0x55555555); // max 2 + a = (a & 0x33333333) + ((a >> 2) & 0x33333333); // max 4 + a = (a + (a >> 4)) & 0x0f0f0f0f; // max 8 per 4, now 8 bits + a = (a + (a >> 8)); // max 16 per 8 bits + a = (a + (a >> 16)); // max 32 per 8 bits + return a & 0xff; +} + +// extract an arbitrarily-aligned N-bit value (N=bits) +// from v, and then make it 8-bits long and fractionally +// extend it to full full range. +static int stbi__shiftsigned(unsigned int v, int shift, int bits) +{ + static unsigned int mul_table[9] = { + 0, + 0xff/*0b11111111*/, 0x55/*0b01010101*/, 0x49/*0b01001001*/, 0x11/*0b00010001*/, + 0x21/*0b00100001*/, 0x41/*0b01000001*/, 0x81/*0b10000001*/, 0x01/*0b00000001*/, + }; + static unsigned int shift_table[9] = { + 0, 0,0,1,0,2,4,6,0, + }; + if (shift < 0) + v <<= -shift; + else + v >>= shift; + STBI_ASSERT(v < 256); + v >>= (8-bits); + STBI_ASSERT(bits >= 0 && bits <= 8); + return (int) ((unsigned) v * mul_table[bits]) >> shift_table[bits]; +} + +typedef struct +{ + int bpp, offset, hsz; + unsigned int mr,mg,mb,ma, all_a; + int extra_read; +} stbi__bmp_data; + +static int stbi__bmp_set_mask_defaults(stbi__bmp_data *info, int compress) +{ + // BI_BITFIELDS specifies masks explicitly, don't override + if (compress == 3) + return 1; + + if (compress == 0) { + if (info->bpp == 16) { + info->mr = 31u << 10; + info->mg = 31u << 5; + info->mb = 31u << 0; + } else if (info->bpp == 32) { + info->mr = 0xffu << 16; + info->mg = 0xffu << 8; + info->mb = 0xffu << 0; + info->ma = 0xffu << 24; + info->all_a = 0; // if all_a is 0 at end, then we loaded alpha channel but it was all 0 + } else { + // otherwise, use defaults, which is all-0 + info->mr = info->mg = info->mb = info->ma = 0; + } + return 1; + } + return 0; // error +} + +static void *stbi__bmp_parse_header(stbi__context *s, stbi__bmp_data *info) +{ + int hsz; + if (stbi__get8(s) != 'B' || stbi__get8(s) != 'M') return stbi__errpuc("not BMP", "Corrupt BMP"); + stbi__get32le(s); // discard filesize + stbi__get16le(s); // discard reserved + stbi__get16le(s); // discard reserved + info->offset = stbi__get32le(s); + info->hsz = hsz = stbi__get32le(s); + info->mr = info->mg = info->mb = info->ma = 0; + info->extra_read = 14; + + if (info->offset < 0) return stbi__errpuc("bad BMP", "bad BMP"); + + if (hsz != 12 && hsz != 40 && hsz != 56 && hsz != 108 && hsz != 124) return stbi__errpuc("unknown BMP", "BMP type not supported: unknown"); + if (hsz == 12) { + s->img_x = stbi__get16le(s); + s->img_y = stbi__get16le(s); + } else { + s->img_x = stbi__get32le(s); + s->img_y = stbi__get32le(s); + } + if (stbi__get16le(s) != 1) return stbi__errpuc("bad BMP", "bad BMP"); + info->bpp = stbi__get16le(s); + if (hsz != 12) { + int compress = stbi__get32le(s); + if (compress == 1 || compress == 2) return stbi__errpuc("BMP RLE", "BMP type not supported: RLE"); + if (compress >= 4) return stbi__errpuc("BMP JPEG/PNG", "BMP type not supported: unsupported compression"); // this includes PNG/JPEG modes + if (compress == 3 && info->bpp != 16 && info->bpp != 32) return stbi__errpuc("bad BMP", "bad BMP"); // bitfields requires 16 or 32 bits/pixel + stbi__get32le(s); // discard sizeof + stbi__get32le(s); // discard hres + stbi__get32le(s); // discard vres + stbi__get32le(s); // discard colorsused + stbi__get32le(s); // discard max important + if (hsz == 40 || hsz == 56) { + if (hsz == 56) { + stbi__get32le(s); + stbi__get32le(s); + stbi__get32le(s); + stbi__get32le(s); + } + if (info->bpp == 16 || info->bpp == 32) { + if (compress == 0) { + stbi__bmp_set_mask_defaults(info, compress); + } else if (compress == 3) { + info->mr = stbi__get32le(s); + info->mg = stbi__get32le(s); + info->mb = stbi__get32le(s); + info->extra_read += 12; + // not documented, but generated by photoshop and handled by mspaint + if (info->mr == info->mg && info->mg == info->mb) { + // ?!?!? + return stbi__errpuc("bad BMP", "bad BMP"); + } + } else + return stbi__errpuc("bad BMP", "bad BMP"); + } + } else { + // V4/V5 header + int i; + if (hsz != 108 && hsz != 124) + return stbi__errpuc("bad BMP", "bad BMP"); + info->mr = stbi__get32le(s); + info->mg = stbi__get32le(s); + info->mb = stbi__get32le(s); + info->ma = stbi__get32le(s); + if (compress != 3) // override mr/mg/mb unless in BI_BITFIELDS mode, as per docs + stbi__bmp_set_mask_defaults(info, compress); + stbi__get32le(s); // discard color space + for (i=0; i < 12; ++i) + stbi__get32le(s); // discard color space parameters + if (hsz == 124) { + stbi__get32le(s); // discard rendering intent + stbi__get32le(s); // discard offset of profile data + stbi__get32le(s); // discard size of profile data + stbi__get32le(s); // discard reserved + } + } + } + return (void *) 1; +} + + +static void *stbi__bmp_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri) +{ + stbi_uc *out; + unsigned int mr=0,mg=0,mb=0,ma=0, all_a; + stbi_uc pal[256][4]; + int psize=0,i,j,width; + int flip_vertically, pad, target; + stbi__bmp_data info; + STBI_NOTUSED(ri); + + info.all_a = 255; + if (stbi__bmp_parse_header(s, &info) == NULL) + return NULL; // error code already set + + flip_vertically = ((int) s->img_y) > 0; + s->img_y = abs((int) s->img_y); + + if (s->img_y > STBI_MAX_DIMENSIONS) return stbi__errpuc("too large","Very large image (corrupt?)"); + if (s->img_x > STBI_MAX_DIMENSIONS) return stbi__errpuc("too large","Very large image (corrupt?)"); + + mr = info.mr; + mg = info.mg; + mb = info.mb; + ma = info.ma; + all_a = info.all_a; + + if (info.hsz == 12) { + if (info.bpp < 24) + psize = (info.offset - info.extra_read - 24) / 3; + } else { + if (info.bpp < 16) + psize = (info.offset - info.extra_read - info.hsz) >> 2; + } + if (psize == 0) { + if (info.offset != s->callback_already_read + (s->img_buffer - s->img_buffer_original)) { + return stbi__errpuc("bad offset", "Corrupt BMP"); + } + } + + if (info.bpp == 24 && ma == 0xff000000) + s->img_n = 3; + else + s->img_n = ma ? 4 : 3; + if (req_comp && req_comp >= 3) // we can directly decode 3 or 4 + target = req_comp; + else + target = s->img_n; // if they want monochrome, we'll post-convert + + // sanity-check size + if (!stbi__mad3sizes_valid(target, s->img_x, s->img_y, 0)) + return stbi__errpuc("too large", "Corrupt BMP"); + + out = (stbi_uc *) stbi__malloc_mad3(target, s->img_x, s->img_y, 0); + if (!out) return stbi__errpuc("outofmem", "Out of memory"); + if (info.bpp < 16) { + int z=0; + if (psize == 0 || psize > 256) { STBI_FREE(out); return stbi__errpuc("invalid", "Corrupt BMP"); } + for (i=0; i < psize; ++i) { + pal[i][2] = stbi__get8(s); + pal[i][1] = stbi__get8(s); + pal[i][0] = stbi__get8(s); + if (info.hsz != 12) stbi__get8(s); + pal[i][3] = 255; + } + stbi__skip(s, info.offset - info.extra_read - info.hsz - psize * (info.hsz == 12 ? 3 : 4)); + if (info.bpp == 1) width = (s->img_x + 7) >> 3; + else if (info.bpp == 4) width = (s->img_x + 1) >> 1; + else if (info.bpp == 8) width = s->img_x; + else { STBI_FREE(out); return stbi__errpuc("bad bpp", "Corrupt BMP"); } + pad = (-width)&3; + if (info.bpp == 1) { + for (j=0; j < (int) s->img_y; ++j) { + int bit_offset = 7, v = stbi__get8(s); + for (i=0; i < (int) s->img_x; ++i) { + int color = (v>>bit_offset)&0x1; + out[z++] = pal[color][0]; + out[z++] = pal[color][1]; + out[z++] = pal[color][2]; + if (target == 4) out[z++] = 255; + if (i+1 == (int) s->img_x) break; + if((--bit_offset) < 0) { + bit_offset = 7; + v = stbi__get8(s); + } + } + stbi__skip(s, pad); + } + } else { + for (j=0; j < (int) s->img_y; ++j) { + for (i=0; i < (int) s->img_x; i += 2) { + int v=stbi__get8(s),v2=0; + if (info.bpp == 4) { + v2 = v & 15; + v >>= 4; + } + out[z++] = pal[v][0]; + out[z++] = pal[v][1]; + out[z++] = pal[v][2]; + if (target == 4) out[z++] = 255; + if (i+1 == (int) s->img_x) break; + v = (info.bpp == 8) ? stbi__get8(s) : v2; + out[z++] = pal[v][0]; + out[z++] = pal[v][1]; + out[z++] = pal[v][2]; + if (target == 4) out[z++] = 255; + } + stbi__skip(s, pad); + } + } + } else { + int rshift=0,gshift=0,bshift=0,ashift=0,rcount=0,gcount=0,bcount=0,acount=0; + int z = 0; + int easy=0; + stbi__skip(s, info.offset - info.extra_read - info.hsz); + if (info.bpp == 24) width = 3 * s->img_x; + else if (info.bpp == 16) width = 2*s->img_x; + else /* bpp = 32 and pad = 0 */ width=0; + pad = (-width) & 3; + if (info.bpp == 24) { + easy = 1; + } else if (info.bpp == 32) { + if (mb == 0xff && mg == 0xff00 && mr == 0x00ff0000 && ma == 0xff000000) + easy = 2; + } + if (!easy) { + if (!mr || !mg || !mb) { STBI_FREE(out); return stbi__errpuc("bad masks", "Corrupt BMP"); } + // right shift amt to put high bit in position #7 + rshift = stbi__high_bit(mr)-7; rcount = stbi__bitcount(mr); + gshift = stbi__high_bit(mg)-7; gcount = stbi__bitcount(mg); + bshift = stbi__high_bit(mb)-7; bcount = stbi__bitcount(mb); + ashift = stbi__high_bit(ma)-7; acount = stbi__bitcount(ma); + if (rcount > 8 || gcount > 8 || bcount > 8 || acount > 8) { STBI_FREE(out); return stbi__errpuc("bad masks", "Corrupt BMP"); } + } + for (j=0; j < (int) s->img_y; ++j) { + if (easy) { + for (i=0; i < (int) s->img_x; ++i) { + unsigned char a; + out[z+2] = stbi__get8(s); + out[z+1] = stbi__get8(s); + out[z+0] = stbi__get8(s); + z += 3; + a = (easy == 2 ? stbi__get8(s) : 255); + all_a |= a; + if (target == 4) out[z++] = a; + } + } else { + int bpp = info.bpp; + for (i=0; i < (int) s->img_x; ++i) { + stbi__uint32 v = (bpp == 16 ? (stbi__uint32) stbi__get16le(s) : stbi__get32le(s)); + unsigned int a; + out[z++] = STBI__BYTECAST(stbi__shiftsigned(v & mr, rshift, rcount)); + out[z++] = STBI__BYTECAST(stbi__shiftsigned(v & mg, gshift, gcount)); + out[z++] = STBI__BYTECAST(stbi__shiftsigned(v & mb, bshift, bcount)); + a = (ma ? stbi__shiftsigned(v & ma, ashift, acount) : 255); + all_a |= a; + if (target == 4) out[z++] = STBI__BYTECAST(a); + } + } + stbi__skip(s, pad); + } + } + + // if alpha channel is all 0s, replace with all 255s + if (target == 4 && all_a == 0) + for (i=4*s->img_x*s->img_y-1; i >= 0; i -= 4) + out[i] = 255; + + if (flip_vertically) { + stbi_uc t; + for (j=0; j < (int) s->img_y>>1; ++j) { + stbi_uc *p1 = out + j *s->img_x*target; + stbi_uc *p2 = out + (s->img_y-1-j)*s->img_x*target; + for (i=0; i < (int) s->img_x*target; ++i) { + t = p1[i]; p1[i] = p2[i]; p2[i] = t; + } + } + } + + if (req_comp && req_comp != target) { + out = stbi__convert_format(out, target, req_comp, s->img_x, s->img_y); + if (out == NULL) return out; // stbi__convert_format frees input on failure + } + + *x = s->img_x; + *y = s->img_y; + if (comp) *comp = s->img_n; + return out; +} +#endif + +// Targa Truevision - TGA +// by Jonathan Dummer +#ifndef STBI_NO_TGA +// returns STBI_rgb or whatever, 0 on error +static int stbi__tga_get_comp(int bits_per_pixel, int is_grey, int* is_rgb16) +{ + // only RGB or RGBA (incl. 16bit) or grey allowed + if (is_rgb16) *is_rgb16 = 0; + switch(bits_per_pixel) { + case 8: return STBI_grey; + case 16: if(is_grey) return STBI_grey_alpha; + // fallthrough + case 15: if(is_rgb16) *is_rgb16 = 1; + return STBI_rgb; + case 24: // fallthrough + case 32: return bits_per_pixel/8; + default: return 0; + } +} + +static int stbi__tga_info(stbi__context *s, int *x, int *y, int *comp) +{ + int tga_w, tga_h, tga_comp, tga_image_type, tga_bits_per_pixel, tga_colormap_bpp; + int sz, tga_colormap_type; + stbi__get8(s); // discard Offset + tga_colormap_type = stbi__get8(s); // colormap type + if( tga_colormap_type > 1 ) { + stbi__rewind(s); + return 0; // only RGB or indexed allowed + } + tga_image_type = stbi__get8(s); // image type + if ( tga_colormap_type == 1 ) { // colormapped (paletted) image + if (tga_image_type != 1 && tga_image_type != 9) { + stbi__rewind(s); + return 0; + } + stbi__skip(s,4); // skip index of first colormap entry and number of entries + sz = stbi__get8(s); // check bits per palette color entry + if ( (sz != 8) && (sz != 15) && (sz != 16) && (sz != 24) && (sz != 32) ) { + stbi__rewind(s); + return 0; + } + stbi__skip(s,4); // skip image x and y origin + tga_colormap_bpp = sz; + } else { // "normal" image w/o colormap - only RGB or grey allowed, +/- RLE + if ( (tga_image_type != 2) && (tga_image_type != 3) && (tga_image_type != 10) && (tga_image_type != 11) ) { + stbi__rewind(s); + return 0; // only RGB or grey allowed, +/- RLE + } + stbi__skip(s,9); // skip colormap specification and image x/y origin + tga_colormap_bpp = 0; + } + tga_w = stbi__get16le(s); + if( tga_w < 1 ) { + stbi__rewind(s); + return 0; // test width + } + tga_h = stbi__get16le(s); + if( tga_h < 1 ) { + stbi__rewind(s); + return 0; // test height + } + tga_bits_per_pixel = stbi__get8(s); // bits per pixel + stbi__get8(s); // ignore alpha bits + if (tga_colormap_bpp != 0) { + if((tga_bits_per_pixel != 8) && (tga_bits_per_pixel != 16)) { + // when using a colormap, tga_bits_per_pixel is the size of the indexes + // I don't think anything but 8 or 16bit indexes makes sense + stbi__rewind(s); + return 0; + } + tga_comp = stbi__tga_get_comp(tga_colormap_bpp, 0, NULL); + } else { + tga_comp = stbi__tga_get_comp(tga_bits_per_pixel, (tga_image_type == 3) || (tga_image_type == 11), NULL); + } + if(!tga_comp) { + stbi__rewind(s); + return 0; + } + if (x) *x = tga_w; + if (y) *y = tga_h; + if (comp) *comp = tga_comp; + return 1; // seems to have passed everything +} + +static int stbi__tga_test(stbi__context *s) +{ + int res = 0; + int sz, tga_color_type; + stbi__get8(s); // discard Offset + tga_color_type = stbi__get8(s); // color type + if ( tga_color_type > 1 ) goto errorEnd; // only RGB or indexed allowed + sz = stbi__get8(s); // image type + if ( tga_color_type == 1 ) { // colormapped (paletted) image + if (sz != 1 && sz != 9) goto errorEnd; // colortype 1 demands image type 1 or 9 + stbi__skip(s,4); // skip index of first colormap entry and number of entries + sz = stbi__get8(s); // check bits per palette color entry + if ( (sz != 8) && (sz != 15) && (sz != 16) && (sz != 24) && (sz != 32) ) goto errorEnd; + stbi__skip(s,4); // skip image x and y origin + } else { // "normal" image w/o colormap + if ( (sz != 2) && (sz != 3) && (sz != 10) && (sz != 11) ) goto errorEnd; // only RGB or grey allowed, +/- RLE + stbi__skip(s,9); // skip colormap specification and image x/y origin + } + if ( stbi__get16le(s) < 1 ) goto errorEnd; // test width + if ( stbi__get16le(s) < 1 ) goto errorEnd; // test height + sz = stbi__get8(s); // bits per pixel + if ( (tga_color_type == 1) && (sz != 8) && (sz != 16) ) goto errorEnd; // for colormapped images, bpp is size of an index + if ( (sz != 8) && (sz != 15) && (sz != 16) && (sz != 24) && (sz != 32) ) goto errorEnd; + + res = 1; // if we got this far, everything's good and we can return 1 instead of 0 + +errorEnd: + stbi__rewind(s); + return res; +} + +// read 16bit value and convert to 24bit RGB +static void stbi__tga_read_rgb16(stbi__context *s, stbi_uc* out) +{ + stbi__uint16 px = (stbi__uint16)stbi__get16le(s); + stbi__uint16 fiveBitMask = 31; + // we have 3 channels with 5bits each + int r = (px >> 10) & fiveBitMask; + int g = (px >> 5) & fiveBitMask; + int b = px & fiveBitMask; + // Note that this saves the data in RGB(A) order, so it doesn't need to be swapped later + out[0] = (stbi_uc)((r * 255)/31); + out[1] = (stbi_uc)((g * 255)/31); + out[2] = (stbi_uc)((b * 255)/31); + + // some people claim that the most significant bit might be used for alpha + // (possibly if an alpha-bit is set in the "image descriptor byte") + // but that only made 16bit test images completely translucent.. + // so let's treat all 15 and 16bit TGAs as RGB with no alpha. +} + +static void *stbi__tga_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri) +{ + // read in the TGA header stuff + int tga_offset = stbi__get8(s); + int tga_indexed = stbi__get8(s); + int tga_image_type = stbi__get8(s); + int tga_is_RLE = 0; + int tga_palette_start = stbi__get16le(s); + int tga_palette_len = stbi__get16le(s); + int tga_palette_bits = stbi__get8(s); + int tga_x_origin = stbi__get16le(s); + int tga_y_origin = stbi__get16le(s); + int tga_width = stbi__get16le(s); + int tga_height = stbi__get16le(s); + int tga_bits_per_pixel = stbi__get8(s); + int tga_comp, tga_rgb16=0; + int tga_inverted = stbi__get8(s); + // int tga_alpha_bits = tga_inverted & 15; // the 4 lowest bits - unused (useless?) + // image data + unsigned char *tga_data; + unsigned char *tga_palette = NULL; + int i, j; + unsigned char raw_data[4] = {0}; + int RLE_count = 0; + int RLE_repeating = 0; + int read_next_pixel = 1; + STBI_NOTUSED(ri); + STBI_NOTUSED(tga_x_origin); // @TODO + STBI_NOTUSED(tga_y_origin); // @TODO + + if (tga_height > STBI_MAX_DIMENSIONS) return stbi__errpuc("too large","Very large image (corrupt?)"); + if (tga_width > STBI_MAX_DIMENSIONS) return stbi__errpuc("too large","Very large image (corrupt?)"); + + // do a tiny bit of precessing + if ( tga_image_type >= 8 ) + { + tga_image_type -= 8; + tga_is_RLE = 1; + } + tga_inverted = 1 - ((tga_inverted >> 5) & 1); + + // If I'm paletted, then I'll use the number of bits from the palette + if ( tga_indexed ) tga_comp = stbi__tga_get_comp(tga_palette_bits, 0, &tga_rgb16); + else tga_comp = stbi__tga_get_comp(tga_bits_per_pixel, (tga_image_type == 3), &tga_rgb16); + + if(!tga_comp) // shouldn't really happen, stbi__tga_test() should have ensured basic consistency + return stbi__errpuc("bad format", "Can't find out TGA pixelformat"); + + // tga info + *x = tga_width; + *y = tga_height; + if (comp) *comp = tga_comp; + + if (!stbi__mad3sizes_valid(tga_width, tga_height, tga_comp, 0)) + return stbi__errpuc("too large", "Corrupt TGA"); + + tga_data = (unsigned char*)stbi__malloc_mad3(tga_width, tga_height, tga_comp, 0); + if (!tga_data) return stbi__errpuc("outofmem", "Out of memory"); + + // skip to the data's starting position (offset usually = 0) + stbi__skip(s, tga_offset ); + + if ( !tga_indexed && !tga_is_RLE && !tga_rgb16 ) { + for (i=0; i < tga_height; ++i) { + int row = tga_inverted ? tga_height -i - 1 : i; + stbi_uc *tga_row = tga_data + row*tga_width*tga_comp; + stbi__getn(s, tga_row, tga_width * tga_comp); + } + } else { + // do I need to load a palette? + if ( tga_indexed) + { + if (tga_palette_len == 0) { /* you have to have at least one entry! */ + STBI_FREE(tga_data); + return stbi__errpuc("bad palette", "Corrupt TGA"); + } + + // any data to skip? (offset usually = 0) + stbi__skip(s, tga_palette_start ); + // load the palette + tga_palette = (unsigned char*)stbi__malloc_mad2(tga_palette_len, tga_comp, 0); + if (!tga_palette) { + STBI_FREE(tga_data); + return stbi__errpuc("outofmem", "Out of memory"); + } + if (tga_rgb16) { + stbi_uc *pal_entry = tga_palette; + STBI_ASSERT(tga_comp == STBI_rgb); + for (i=0; i < tga_palette_len; ++i) { + stbi__tga_read_rgb16(s, pal_entry); + pal_entry += tga_comp; + } + } else if (!stbi__getn(s, tga_palette, tga_palette_len * tga_comp)) { + STBI_FREE(tga_data); + STBI_FREE(tga_palette); + return stbi__errpuc("bad palette", "Corrupt TGA"); + } + } + // load the data + for (i=0; i < tga_width * tga_height; ++i) + { + // if I'm in RLE mode, do I need to get a RLE stbi__pngchunk? + if ( tga_is_RLE ) + { + if ( RLE_count == 0 ) + { + // yep, get the next byte as a RLE command + int RLE_cmd = stbi__get8(s); + RLE_count = 1 + (RLE_cmd & 127); + RLE_repeating = RLE_cmd >> 7; + read_next_pixel = 1; + } else if ( !RLE_repeating ) + { + read_next_pixel = 1; + } + } else + { + read_next_pixel = 1; + } + // OK, if I need to read a pixel, do it now + if ( read_next_pixel ) + { + // load however much data we did have + if ( tga_indexed ) + { + // read in index, then perform the lookup + int pal_idx = (tga_bits_per_pixel == 8) ? stbi__get8(s) : stbi__get16le(s); + if ( pal_idx >= tga_palette_len ) { + // invalid index + pal_idx = 0; + } + pal_idx *= tga_comp; + for (j = 0; j < tga_comp; ++j) { + raw_data[j] = tga_palette[pal_idx+j]; + } + } else if(tga_rgb16) { + STBI_ASSERT(tga_comp == STBI_rgb); + stbi__tga_read_rgb16(s, raw_data); + } else { + // read in the data raw + for (j = 0; j < tga_comp; ++j) { + raw_data[j] = stbi__get8(s); + } + } + // clear the reading flag for the next pixel + read_next_pixel = 0; + } // end of reading a pixel + + // copy data + for (j = 0; j < tga_comp; ++j) + tga_data[i*tga_comp+j] = raw_data[j]; + + // in case we're in RLE mode, keep counting down + --RLE_count; + } + // do I need to invert the image? + if ( tga_inverted ) + { + for (j = 0; j*2 < tga_height; ++j) + { + int index1 = j * tga_width * tga_comp; + int index2 = (tga_height - 1 - j) * tga_width * tga_comp; + for (i = tga_width * tga_comp; i > 0; --i) + { + unsigned char temp = tga_data[index1]; + tga_data[index1] = tga_data[index2]; + tga_data[index2] = temp; + ++index1; + ++index2; + } + } + } + // clear my palette, if I had one + if ( tga_palette != NULL ) + { + STBI_FREE( tga_palette ); + } + } + + // swap RGB - if the source data was RGB16, it already is in the right order + if (tga_comp >= 3 && !tga_rgb16) + { + unsigned char* tga_pixel = tga_data; + for (i=0; i < tga_width * tga_height; ++i) + { + unsigned char temp = tga_pixel[0]; + tga_pixel[0] = tga_pixel[2]; + tga_pixel[2] = temp; + tga_pixel += tga_comp; + } + } + + // convert to target component count + if (req_comp && req_comp != tga_comp) + tga_data = stbi__convert_format(tga_data, tga_comp, req_comp, tga_width, tga_height); + + // the things I do to get rid of an error message, and yet keep + // Microsoft's C compilers happy... [8^( + tga_palette_start = tga_palette_len = tga_palette_bits = + tga_x_origin = tga_y_origin = 0; + STBI_NOTUSED(tga_palette_start); + // OK, done + return tga_data; +} +#endif + +// ************************************************************************************************* +// Photoshop PSD loader -- PD by Thatcher Ulrich, integration by Nicolas Schulz, tweaked by STB + +#ifndef STBI_NO_PSD +static int stbi__psd_test(stbi__context *s) +{ + int r = (stbi__get32be(s) == 0x38425053); + stbi__rewind(s); + return r; +} + +static int stbi__psd_decode_rle(stbi__context *s, stbi_uc *p, int pixelCount) +{ + int count, nleft, len; + + count = 0; + while ((nleft = pixelCount - count) > 0) { + len = stbi__get8(s); + if (len == 128) { + // No-op. + } else if (len < 128) { + // Copy next len+1 bytes literally. + len++; + if (len > nleft) return 0; // corrupt data + count += len; + while (len) { + *p = stbi__get8(s); + p += 4; + len--; + } + } else if (len > 128) { + stbi_uc val; + // Next -len+1 bytes in the dest are replicated from next source byte. + // (Interpret len as a negative 8-bit int.) + len = 257 - len; + if (len > nleft) return 0; // corrupt data + val = stbi__get8(s); + count += len; + while (len) { + *p = val; + p += 4; + len--; + } + } + } + + return 1; +} + +static void *stbi__psd_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri, int bpc) +{ + int pixelCount; + int channelCount, compression; + int channel, i; + int bitdepth; + int w,h; + stbi_uc *out; + STBI_NOTUSED(ri); + + // Check identifier + if (stbi__get32be(s) != 0x38425053) // "8BPS" + return stbi__errpuc("not PSD", "Corrupt PSD image"); + + // Check file type version. + if (stbi__get16be(s) != 1) + return stbi__errpuc("wrong version", "Unsupported version of PSD image"); + + // Skip 6 reserved bytes. + stbi__skip(s, 6 ); + + // Read the number of channels (R, G, B, A, etc). + channelCount = stbi__get16be(s); + if (channelCount < 0 || channelCount > 16) + return stbi__errpuc("wrong channel count", "Unsupported number of channels in PSD image"); + + // Read the rows and columns of the image. + h = stbi__get32be(s); + w = stbi__get32be(s); + + if (h > STBI_MAX_DIMENSIONS) return stbi__errpuc("too large","Very large image (corrupt?)"); + if (w > STBI_MAX_DIMENSIONS) return stbi__errpuc("too large","Very large image (corrupt?)"); + + // Make sure the depth is 8 bits. + bitdepth = stbi__get16be(s); + if (bitdepth != 8 && bitdepth != 16) + return stbi__errpuc("unsupported bit depth", "PSD bit depth is not 8 or 16 bit"); + + // Make sure the color mode is RGB. + // Valid options are: + // 0: Bitmap + // 1: Grayscale + // 2: Indexed color + // 3: RGB color + // 4: CMYK color + // 7: Multichannel + // 8: Duotone + // 9: Lab color + if (stbi__get16be(s) != 3) + return stbi__errpuc("wrong color format", "PSD is not in RGB color format"); + + // Skip the Mode Data. (It's the palette for indexed color; other info for other modes.) + stbi__skip(s,stbi__get32be(s) ); + + // Skip the image resources. (resolution, pen tool paths, etc) + stbi__skip(s, stbi__get32be(s) ); + + // Skip the reserved data. + stbi__skip(s, stbi__get32be(s) ); + + // Find out if the data is compressed. + // Known values: + // 0: no compression + // 1: RLE compressed + compression = stbi__get16be(s); + if (compression > 1) + return stbi__errpuc("bad compression", "PSD has an unknown compression format"); + + // Check size + if (!stbi__mad3sizes_valid(4, w, h, 0)) + return stbi__errpuc("too large", "Corrupt PSD"); + + // Create the destination image. + + if (!compression && bitdepth == 16 && bpc == 16) { + out = (stbi_uc *) stbi__malloc_mad3(8, w, h, 0); + ri->bits_per_channel = 16; + } else + out = (stbi_uc *) stbi__malloc(4 * w*h); + + if (!out) return stbi__errpuc("outofmem", "Out of memory"); + pixelCount = w*h; + + // Initialize the data to zero. + //memset( out, 0, pixelCount * 4 ); + + // Finally, the image data. + if (compression) { + // RLE as used by .PSD and .TIFF + // Loop until you get the number of unpacked bytes you are expecting: + // Read the next source byte into n. + // If n is between 0 and 127 inclusive, copy the next n+1 bytes literally. + // Else if n is between -127 and -1 inclusive, copy the next byte -n+1 times. + // Else if n is 128, noop. + // Endloop + + // The RLE-compressed data is preceded by a 2-byte data count for each row in the data, + // which we're going to just skip. + stbi__skip(s, h * channelCount * 2 ); + + // Read the RLE data by channel. + for (channel = 0; channel < 4; channel++) { + stbi_uc *p; + + p = out+channel; + if (channel >= channelCount) { + // Fill this channel with default data. + for (i = 0; i < pixelCount; i++, p += 4) + *p = (channel == 3 ? 255 : 0); + } else { + // Read the RLE data. + if (!stbi__psd_decode_rle(s, p, pixelCount)) { + STBI_FREE(out); + return stbi__errpuc("corrupt", "bad RLE data"); + } + } + } + + } else { + // We're at the raw image data. It's each channel in order (Red, Green, Blue, Alpha, ...) + // where each channel consists of an 8-bit (or 16-bit) value for each pixel in the image. + + // Read the data by channel. + for (channel = 0; channel < 4; channel++) { + if (channel >= channelCount) { + // Fill this channel with default data. + if (bitdepth == 16 && bpc == 16) { + stbi__uint16 *q = ((stbi__uint16 *) out) + channel; + stbi__uint16 val = channel == 3 ? 65535 : 0; + for (i = 0; i < pixelCount; i++, q += 4) + *q = val; + } else { + stbi_uc *p = out+channel; + stbi_uc val = channel == 3 ? 255 : 0; + for (i = 0; i < pixelCount; i++, p += 4) + *p = val; + } + } else { + if (ri->bits_per_channel == 16) { // output bpc + stbi__uint16 *q = ((stbi__uint16 *) out) + channel; + for (i = 0; i < pixelCount; i++, q += 4) + *q = (stbi__uint16) stbi__get16be(s); + } else { + stbi_uc *p = out+channel; + if (bitdepth == 16) { // input bpc + for (i = 0; i < pixelCount; i++, p += 4) + *p = (stbi_uc) (stbi__get16be(s) >> 8); + } else { + for (i = 0; i < pixelCount; i++, p += 4) + *p = stbi__get8(s); + } + } + } + } + } + + // remove weird white matte from PSD + if (channelCount >= 4) { + if (ri->bits_per_channel == 16) { + for (i=0; i < w*h; ++i) { + stbi__uint16 *pixel = (stbi__uint16 *) out + 4*i; + if (pixel[3] != 0 && pixel[3] != 65535) { + float a = pixel[3] / 65535.0f; + float ra = 1.0f / a; + float inv_a = 65535.0f * (1 - ra); + pixel[0] = (stbi__uint16) (pixel[0]*ra + inv_a); + pixel[1] = (stbi__uint16) (pixel[1]*ra + inv_a); + pixel[2] = (stbi__uint16) (pixel[2]*ra + inv_a); + } + } + } else { + for (i=0; i < w*h; ++i) { + unsigned char *pixel = out + 4*i; + if (pixel[3] != 0 && pixel[3] != 255) { + float a = pixel[3] / 255.0f; + float ra = 1.0f / a; + float inv_a = 255.0f * (1 - ra); + pixel[0] = (unsigned char) (pixel[0]*ra + inv_a); + pixel[1] = (unsigned char) (pixel[1]*ra + inv_a); + pixel[2] = (unsigned char) (pixel[2]*ra + inv_a); + } + } + } + } + + // convert to desired output format + if (req_comp && req_comp != 4) { + if (ri->bits_per_channel == 16) + out = (stbi_uc *) stbi__convert_format16((stbi__uint16 *) out, 4, req_comp, w, h); + else + out = stbi__convert_format(out, 4, req_comp, w, h); + if (out == NULL) return out; // stbi__convert_format frees input on failure + } + + if (comp) *comp = 4; + *y = h; + *x = w; + + return out; +} +#endif + +// ************************************************************************************************* +// Softimage PIC loader +// by Tom Seddon +// +// See http://softimage.wiki.softimage.com/index.php/INFO:_PIC_file_format +// See http://ozviz.wasp.uwa.edu.au/~pbourke/dataformats/softimagepic/ + +#ifndef STBI_NO_PIC +static int stbi__pic_is4(stbi__context *s,const char *str) +{ + int i; + for (i=0; i<4; ++i) + if (stbi__get8(s) != (stbi_uc)str[i]) + return 0; + + return 1; +} + +static int stbi__pic_test_core(stbi__context *s) +{ + int i; + + if (!stbi__pic_is4(s,"\x53\x80\xF6\x34")) + return 0; + + for(i=0;i<84;++i) + stbi__get8(s); + + if (!stbi__pic_is4(s,"PICT")) + return 0; + + return 1; +} + +typedef struct +{ + stbi_uc size,type,channel; +} stbi__pic_packet; + +static stbi_uc *stbi__readval(stbi__context *s, int channel, stbi_uc *dest) +{ + int mask=0x80, i; + + for (i=0; i<4; ++i, mask>>=1) { + if (channel & mask) { + if (stbi__at_eof(s)) return stbi__errpuc("bad file","PIC file too short"); + dest[i]=stbi__get8(s); + } + } + + return dest; +} + +static void stbi__copyval(int channel,stbi_uc *dest,const stbi_uc *src) +{ + int mask=0x80,i; + + for (i=0;i<4; ++i, mask>>=1) + if (channel&mask) + dest[i]=src[i]; +} + +static stbi_uc *stbi__pic_load_core(stbi__context *s,int width,int height,int *comp, stbi_uc *result) +{ + int act_comp=0,num_packets=0,y,chained; + stbi__pic_packet packets[10]; + + // this will (should...) cater for even some bizarre stuff like having data + // for the same channel in multiple packets. + do { + stbi__pic_packet *packet; + + if (num_packets==sizeof(packets)/sizeof(packets[0])) + return stbi__errpuc("bad format","too many packets"); + + packet = &packets[num_packets++]; + + chained = stbi__get8(s); + packet->size = stbi__get8(s); + packet->type = stbi__get8(s); + packet->channel = stbi__get8(s); + + act_comp |= packet->channel; + + if (stbi__at_eof(s)) return stbi__errpuc("bad file","file too short (reading packets)"); + if (packet->size != 8) return stbi__errpuc("bad format","packet isn't 8bpp"); + } while (chained); + + *comp = (act_comp & 0x10 ? 4 : 3); // has alpha channel? + + for(y=0; ytype) { + default: + return stbi__errpuc("bad format","packet has bad compression type"); + + case 0: {//uncompressed + int x; + + for(x=0;xchannel,dest)) + return 0; + break; + } + + case 1://Pure RLE + { + int left=width, i; + + while (left>0) { + stbi_uc count,value[4]; + + count=stbi__get8(s); + if (stbi__at_eof(s)) return stbi__errpuc("bad file","file too short (pure read count)"); + + if (count > left) + count = (stbi_uc) left; + + if (!stbi__readval(s,packet->channel,value)) return 0; + + for(i=0; ichannel,dest,value); + left -= count; + } + } + break; + + case 2: {//Mixed RLE + int left=width; + while (left>0) { + int count = stbi__get8(s), i; + if (stbi__at_eof(s)) return stbi__errpuc("bad file","file too short (mixed read count)"); + + if (count >= 128) { // Repeated + stbi_uc value[4]; + + if (count==128) + count = stbi__get16be(s); + else + count -= 127; + if (count > left) + return stbi__errpuc("bad file","scanline overrun"); + + if (!stbi__readval(s,packet->channel,value)) + return 0; + + for(i=0;ichannel,dest,value); + } else { // Raw + ++count; + if (count>left) return stbi__errpuc("bad file","scanline overrun"); + + for(i=0;ichannel,dest)) + return 0; + } + left-=count; + } + break; + } + } + } + } + + return result; +} + +static void *stbi__pic_load(stbi__context *s,int *px,int *py,int *comp,int req_comp, stbi__result_info *ri) +{ + stbi_uc *result; + int i, x,y, internal_comp; + STBI_NOTUSED(ri); + + if (!comp) comp = &internal_comp; + + for (i=0; i<92; ++i) + stbi__get8(s); + + x = stbi__get16be(s); + y = stbi__get16be(s); + + if (y > STBI_MAX_DIMENSIONS) return stbi__errpuc("too large","Very large image (corrupt?)"); + if (x > STBI_MAX_DIMENSIONS) return stbi__errpuc("too large","Very large image (corrupt?)"); + + if (stbi__at_eof(s)) return stbi__errpuc("bad file","file too short (pic header)"); + if (!stbi__mad3sizes_valid(x, y, 4, 0)) return stbi__errpuc("too large", "PIC image too large to decode"); + + stbi__get32be(s); //skip `ratio' + stbi__get16be(s); //skip `fields' + stbi__get16be(s); //skip `pad' + + // intermediate buffer is RGBA + result = (stbi_uc *) stbi__malloc_mad3(x, y, 4, 0); + if (!result) return stbi__errpuc("outofmem", "Out of memory"); + memset(result, 0xff, x*y*4); + + if (!stbi__pic_load_core(s,x,y,comp, result)) { + STBI_FREE(result); + result=0; + } + *px = x; + *py = y; + if (req_comp == 0) req_comp = *comp; + result=stbi__convert_format(result,4,req_comp,x,y); + + return result; +} + +static int stbi__pic_test(stbi__context *s) +{ + int r = stbi__pic_test_core(s); + stbi__rewind(s); + return r; +} +#endif + +// ************************************************************************************************* +// GIF loader -- public domain by Jean-Marc Lienher -- simplified/shrunk by stb + +#ifndef STBI_NO_GIF +typedef struct +{ + stbi__int16 prefix; + stbi_uc first; + stbi_uc suffix; +} stbi__gif_lzw; + +typedef struct +{ + int w,h; + stbi_uc *out; // output buffer (always 4 components) + stbi_uc *background; // The current "background" as far as a gif is concerned + stbi_uc *history; + int flags, bgindex, ratio, transparent, eflags; + stbi_uc pal[256][4]; + stbi_uc lpal[256][4]; + stbi__gif_lzw codes[8192]; + stbi_uc *color_table; + int parse, step; + int lflags; + int start_x, start_y; + int max_x, max_y; + int cur_x, cur_y; + int line_size; + int delay; +} stbi__gif; + +static int stbi__gif_test_raw(stbi__context *s) +{ + int sz; + if (stbi__get8(s) != 'G' || stbi__get8(s) != 'I' || stbi__get8(s) != 'F' || stbi__get8(s) != '8') return 0; + sz = stbi__get8(s); + if (sz != '9' && sz != '7') return 0; + if (stbi__get8(s) != 'a') return 0; + return 1; +} + +static int stbi__gif_test(stbi__context *s) +{ + int r = stbi__gif_test_raw(s); + stbi__rewind(s); + return r; +} + +static void stbi__gif_parse_colortable(stbi__context *s, stbi_uc pal[256][4], int num_entries, int transp) +{ + int i; + for (i=0; i < num_entries; ++i) { + pal[i][2] = stbi__get8(s); + pal[i][1] = stbi__get8(s); + pal[i][0] = stbi__get8(s); + pal[i][3] = transp == i ? 0 : 255; + } +} + +static int stbi__gif_header(stbi__context *s, stbi__gif *g, int *comp, int is_info) +{ + stbi_uc version; + if (stbi__get8(s) != 'G' || stbi__get8(s) != 'I' || stbi__get8(s) != 'F' || stbi__get8(s) != '8') + return stbi__err("not GIF", "Corrupt GIF"); + + version = stbi__get8(s); + if (version != '7' && version != '9') return stbi__err("not GIF", "Corrupt GIF"); + if (stbi__get8(s) != 'a') return stbi__err("not GIF", "Corrupt GIF"); + + stbi__g_failure_reason = ""; + g->w = stbi__get16le(s); + g->h = stbi__get16le(s); + g->flags = stbi__get8(s); + g->bgindex = stbi__get8(s); + g->ratio = stbi__get8(s); + g->transparent = -1; + + if (g->w > STBI_MAX_DIMENSIONS) return stbi__err("too large","Very large image (corrupt?)"); + if (g->h > STBI_MAX_DIMENSIONS) return stbi__err("too large","Very large image (corrupt?)"); + + if (comp != 0) *comp = 4; // can't actually tell whether it's 3 or 4 until we parse the comments + + if (is_info) return 1; + + if (g->flags & 0x80) + stbi__gif_parse_colortable(s,g->pal, 2 << (g->flags & 7), -1); + + return 1; +} + +static int stbi__gif_info_raw(stbi__context *s, int *x, int *y, int *comp) +{ + stbi__gif* g = (stbi__gif*) stbi__malloc(sizeof(stbi__gif)); + if (!g) return stbi__err("outofmem", "Out of memory"); + if (!stbi__gif_header(s, g, comp, 1)) { + STBI_FREE(g); + stbi__rewind( s ); + return 0; + } + if (x) *x = g->w; + if (y) *y = g->h; + STBI_FREE(g); + return 1; +} + +static void stbi__out_gif_code(stbi__gif *g, stbi__uint16 code) +{ + stbi_uc *p, *c; + int idx; + + // recurse to decode the prefixes, since the linked-list is backwards, + // and working backwards through an interleaved image would be nasty + if (g->codes[code].prefix >= 0) + stbi__out_gif_code(g, g->codes[code].prefix); + + if (g->cur_y >= g->max_y) return; + + idx = g->cur_x + g->cur_y; + p = &g->out[idx]; + g->history[idx / 4] = 1; + + c = &g->color_table[g->codes[code].suffix * 4]; + if (c[3] > 128) { // don't render transparent pixels; + p[0] = c[2]; + p[1] = c[1]; + p[2] = c[0]; + p[3] = c[3]; + } + g->cur_x += 4; + + if (g->cur_x >= g->max_x) { + g->cur_x = g->start_x; + g->cur_y += g->step; + + while (g->cur_y >= g->max_y && g->parse > 0) { + g->step = (1 << g->parse) * g->line_size; + g->cur_y = g->start_y + (g->step >> 1); + --g->parse; + } + } +} + +static stbi_uc *stbi__process_gif_raster(stbi__context *s, stbi__gif *g) +{ + stbi_uc lzw_cs; + stbi__int32 len, init_code; + stbi__uint32 first; + stbi__int32 codesize, codemask, avail, oldcode, bits, valid_bits, clear; + stbi__gif_lzw *p; + + lzw_cs = stbi__get8(s); + if (lzw_cs > 12) return NULL; + clear = 1 << lzw_cs; + first = 1; + codesize = lzw_cs + 1; + codemask = (1 << codesize) - 1; + bits = 0; + valid_bits = 0; + for (init_code = 0; init_code < clear; init_code++) { + g->codes[init_code].prefix = -1; + g->codes[init_code].first = (stbi_uc) init_code; + g->codes[init_code].suffix = (stbi_uc) init_code; + } + + // support no starting clear code + avail = clear+2; + oldcode = -1; + + len = 0; + for(;;) { + if (valid_bits < codesize) { + if (len == 0) { + len = stbi__get8(s); // start new block + if (len == 0) + return g->out; + } + --len; + bits |= (stbi__int32) stbi__get8(s) << valid_bits; + valid_bits += 8; + } else { + stbi__int32 code = bits & codemask; + bits >>= codesize; + valid_bits -= codesize; + // @OPTIMIZE: is there some way we can accelerate the non-clear path? + if (code == clear) { // clear code + codesize = lzw_cs + 1; + codemask = (1 << codesize) - 1; + avail = clear + 2; + oldcode = -1; + first = 0; + } else if (code == clear + 1) { // end of stream code + stbi__skip(s, len); + while ((len = stbi__get8(s)) > 0) + stbi__skip(s,len); + return g->out; + } else if (code <= avail) { + if (first) { + return stbi__errpuc("no clear code", "Corrupt GIF"); + } + + if (oldcode >= 0) { + p = &g->codes[avail++]; + if (avail > 8192) { + return stbi__errpuc("too many codes", "Corrupt GIF"); + } + + p->prefix = (stbi__int16) oldcode; + p->first = g->codes[oldcode].first; + p->suffix = (code == avail) ? p->first : g->codes[code].first; + } else if (code == avail) + return stbi__errpuc("illegal code in raster", "Corrupt GIF"); + + stbi__out_gif_code(g, (stbi__uint16) code); + + if ((avail & codemask) == 0 && avail <= 0x0FFF) { + codesize++; + codemask = (1 << codesize) - 1; + } + + oldcode = code; + } else { + return stbi__errpuc("illegal code in raster", "Corrupt GIF"); + } + } + } +} + +// this function is designed to support animated gifs, although stb_image doesn't support it +// two back is the image from two frames ago, used for a very specific disposal format +static stbi_uc *stbi__gif_load_next(stbi__context *s, stbi__gif *g, int *comp, int req_comp, stbi_uc *two_back) +{ + int dispose; + int first_frame; + int pi; + int pcount; + STBI_NOTUSED(req_comp); + + // on first frame, any non-written pixels get the background colour (non-transparent) + first_frame = 0; + if (g->out == 0) { + if (!stbi__gif_header(s, g, comp,0)) return 0; // stbi__g_failure_reason set by stbi__gif_header + if (!stbi__mad3sizes_valid(4, g->w, g->h, 0)) + return stbi__errpuc("too large", "GIF image is too large"); + pcount = g->w * g->h; + g->out = (stbi_uc *) stbi__malloc(4 * pcount); + g->background = (stbi_uc *) stbi__malloc(4 * pcount); + g->history = (stbi_uc *) stbi__malloc(pcount); + if (!g->out || !g->background || !g->history) + return stbi__errpuc("outofmem", "Out of memory"); + + // image is treated as "transparent" at the start - ie, nothing overwrites the current background; + // background colour is only used for pixels that are not rendered first frame, after that "background" + // color refers to the color that was there the previous frame. + memset(g->out, 0x00, 4 * pcount); + memset(g->background, 0x00, 4 * pcount); // state of the background (starts transparent) + memset(g->history, 0x00, pcount); // pixels that were affected previous frame + first_frame = 1; + } else { + // second frame - how do we dispose of the previous one? + dispose = (g->eflags & 0x1C) >> 2; + pcount = g->w * g->h; + + if ((dispose == 3) && (two_back == 0)) { + dispose = 2; // if I don't have an image to revert back to, default to the old background + } + + if (dispose == 3) { // use previous graphic + for (pi = 0; pi < pcount; ++pi) { + if (g->history[pi]) { + memcpy( &g->out[pi * 4], &two_back[pi * 4], 4 ); + } + } + } else if (dispose == 2) { + // restore what was changed last frame to background before that frame; + for (pi = 0; pi < pcount; ++pi) { + if (g->history[pi]) { + memcpy( &g->out[pi * 4], &g->background[pi * 4], 4 ); + } + } + } else { + // This is a non-disposal case eithe way, so just + // leave the pixels as is, and they will become the new background + // 1: do not dispose + // 0: not specified. + } + + // background is what out is after the undoing of the previou frame; + memcpy( g->background, g->out, 4 * g->w * g->h ); + } + + // clear my history; + memset( g->history, 0x00, g->w * g->h ); // pixels that were affected previous frame + + for (;;) { + int tag = stbi__get8(s); + switch (tag) { + case 0x2C: /* Image Descriptor */ + { + stbi__int32 x, y, w, h; + stbi_uc *o; + + x = stbi__get16le(s); + y = stbi__get16le(s); + w = stbi__get16le(s); + h = stbi__get16le(s); + if (((x + w) > (g->w)) || ((y + h) > (g->h))) + return stbi__errpuc("bad Image Descriptor", "Corrupt GIF"); + + g->line_size = g->w * 4; + g->start_x = x * 4; + g->start_y = y * g->line_size; + g->max_x = g->start_x + w * 4; + g->max_y = g->start_y + h * g->line_size; + g->cur_x = g->start_x; + g->cur_y = g->start_y; + + // if the width of the specified rectangle is 0, that means + // we may not see *any* pixels or the image is malformed; + // to make sure this is caught, move the current y down to + // max_y (which is what out_gif_code checks). + if (w == 0) + g->cur_y = g->max_y; + + g->lflags = stbi__get8(s); + + if (g->lflags & 0x40) { + g->step = 8 * g->line_size; // first interlaced spacing + g->parse = 3; + } else { + g->step = g->line_size; + g->parse = 0; + } + + if (g->lflags & 0x80) { + stbi__gif_parse_colortable(s,g->lpal, 2 << (g->lflags & 7), g->eflags & 0x01 ? g->transparent : -1); + g->color_table = (stbi_uc *) g->lpal; + } else if (g->flags & 0x80) { + g->color_table = (stbi_uc *) g->pal; + } else + return stbi__errpuc("missing color table", "Corrupt GIF"); + + o = stbi__process_gif_raster(s, g); + if (!o) return NULL; + + // if this was the first frame, + pcount = g->w * g->h; + if (first_frame && (g->bgindex > 0)) { + // if first frame, any pixel not drawn to gets the background color + for (pi = 0; pi < pcount; ++pi) { + if (g->history[pi] == 0) { + g->pal[g->bgindex][3] = 255; // just in case it was made transparent, undo that; It will be reset next frame if need be; + memcpy( &g->out[pi * 4], &g->pal[g->bgindex], 4 ); + } + } + } + + return o; + } + + case 0x21: // Comment Extension. + { + int len; + int ext = stbi__get8(s); + if (ext == 0xF9) { // Graphic Control Extension. + len = stbi__get8(s); + if (len == 4) { + g->eflags = stbi__get8(s); + g->delay = 10 * stbi__get16le(s); // delay - 1/100th of a second, saving as 1/1000ths. + + // unset old transparent + if (g->transparent >= 0) { + g->pal[g->transparent][3] = 255; + } + if (g->eflags & 0x01) { + g->transparent = stbi__get8(s); + if (g->transparent >= 0) { + g->pal[g->transparent][3] = 0; + } + } else { + // don't need transparent + stbi__skip(s, 1); + g->transparent = -1; + } + } else { + stbi__skip(s, len); + break; + } + } + while ((len = stbi__get8(s)) != 0) { + stbi__skip(s, len); + } + break; + } + + case 0x3B: // gif stream termination code + return (stbi_uc *) s; // using '1' causes warning on some compilers + + default: + return stbi__errpuc("unknown code", "Corrupt GIF"); + } + } +} + +static void *stbi__load_gif_main_outofmem(stbi__gif *g, stbi_uc *out, int **delays) +{ + STBI_FREE(g->out); + STBI_FREE(g->history); + STBI_FREE(g->background); + + if (out) STBI_FREE(out); + if (delays && *delays) STBI_FREE(*delays); + return stbi__errpuc("outofmem", "Out of memory"); +} + +static void *stbi__load_gif_main(stbi__context *s, int **delays, int *x, int *y, int *z, int *comp, int req_comp) +{ + if (stbi__gif_test(s)) { + int layers = 0; + stbi_uc *u = 0; + stbi_uc *out = 0; + stbi_uc *two_back = 0; + stbi__gif g; + int stride; + int out_size = 0; + int delays_size = 0; + + STBI_NOTUSED(out_size); + STBI_NOTUSED(delays_size); + + memset(&g, 0, sizeof(g)); + if (delays) { + *delays = 0; + } + + do { + u = stbi__gif_load_next(s, &g, comp, req_comp, two_back); + if (u == (stbi_uc *) s) u = 0; // end of animated gif marker + + if (u) { + *x = g.w; + *y = g.h; + ++layers; + stride = g.w * g.h * 4; + + if (out) { + void *tmp = (stbi_uc*) STBI_REALLOC_SIZED( out, out_size, layers * stride ); + if (!tmp) + return stbi__load_gif_main_outofmem(&g, out, delays); + else { + out = (stbi_uc*) tmp; + out_size = layers * stride; + } + + if (delays) { + int *new_delays = (int*) STBI_REALLOC_SIZED( *delays, delays_size, sizeof(int) * layers ); + if (!new_delays) + return stbi__load_gif_main_outofmem(&g, out, delays); + *delays = new_delays; + delays_size = layers * sizeof(int); + } + } else { + out = (stbi_uc*)stbi__malloc( layers * stride ); + if (!out) + return stbi__load_gif_main_outofmem(&g, out, delays); + out_size = layers * stride; + if (delays) { + *delays = (int*) stbi__malloc( layers * sizeof(int) ); + if (!*delays) + return stbi__load_gif_main_outofmem(&g, out, delays); + delays_size = layers * sizeof(int); + } + } + memcpy( out + ((layers - 1) * stride), u, stride ); + if (layers >= 2) { + two_back = out - 2 * stride; + } + + if (delays) { + (*delays)[layers - 1U] = g.delay; + } + } + } while (u != 0); + + // free temp buffer; + STBI_FREE(g.out); + STBI_FREE(g.history); + STBI_FREE(g.background); + + // do the final conversion after loading everything; + if (req_comp && req_comp != 4) + out = stbi__convert_format(out, 4, req_comp, layers * g.w, g.h); + + *z = layers; + return out; + } else { + return stbi__errpuc("not GIF", "Image was not as a gif type."); + } +} + +static void *stbi__gif_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri) +{ + stbi_uc *u = 0; + stbi__gif g; + memset(&g, 0, sizeof(g)); + STBI_NOTUSED(ri); + + u = stbi__gif_load_next(s, &g, comp, req_comp, 0); + if (u == (stbi_uc *) s) u = 0; // end of animated gif marker + if (u) { + *x = g.w; + *y = g.h; + + // moved conversion to after successful load so that the same + // can be done for multiple frames. + if (req_comp && req_comp != 4) + u = stbi__convert_format(u, 4, req_comp, g.w, g.h); + } else if (g.out) { + // if there was an error and we allocated an image buffer, free it! + STBI_FREE(g.out); + } + + // free buffers needed for multiple frame loading; + STBI_FREE(g.history); + STBI_FREE(g.background); + + return u; +} + +static int stbi__gif_info(stbi__context *s, int *x, int *y, int *comp) +{ + return stbi__gif_info_raw(s,x,y,comp); +} +#endif + +// ************************************************************************************************* +// Radiance RGBE HDR loader +// originally by Nicolas Schulz +#ifndef STBI_NO_HDR +static int stbi__hdr_test_core(stbi__context *s, const char *signature) +{ + int i; + for (i=0; signature[i]; ++i) + if (stbi__get8(s) != signature[i]) + return 0; + stbi__rewind(s); + return 1; +} + +static int stbi__hdr_test(stbi__context* s) +{ + int r = stbi__hdr_test_core(s, "#?RADIANCE\n"); + stbi__rewind(s); + if(!r) { + r = stbi__hdr_test_core(s, "#?RGBE\n"); + stbi__rewind(s); + } + return r; +} + +#define STBI__HDR_BUFLEN 1024 +static char *stbi__hdr_gettoken(stbi__context *z, char *buffer) +{ + int len=0; + char c = '\0'; + + c = (char) stbi__get8(z); + + while (!stbi__at_eof(z) && c != '\n') { + buffer[len++] = c; + if (len == STBI__HDR_BUFLEN-1) { + // flush to end of line + while (!stbi__at_eof(z) && stbi__get8(z) != '\n') + ; + break; + } + c = (char) stbi__get8(z); + } + + buffer[len] = 0; + return buffer; +} + +static void stbi__hdr_convert(float *output, stbi_uc *input, int req_comp) +{ + if ( input[3] != 0 ) { + float f1; + // Exponent + f1 = (float) ldexp(1.0f, input[3] - (int)(128 + 8)); + if (req_comp <= 2) + output[0] = (input[0] + input[1] + input[2]) * f1 / 3; + else { + output[0] = input[0] * f1; + output[1] = input[1] * f1; + output[2] = input[2] * f1; + } + if (req_comp == 2) output[1] = 1; + if (req_comp == 4) output[3] = 1; + } else { + switch (req_comp) { + case 4: output[3] = 1; /* fallthrough */ + case 3: output[0] = output[1] = output[2] = 0; + break; + case 2: output[1] = 1; /* fallthrough */ + case 1: output[0] = 0; + break; + } + } +} + +static float *stbi__hdr_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri) +{ + char buffer[STBI__HDR_BUFLEN]; + char *token; + int valid = 0; + int width, height; + stbi_uc *scanline; + float *hdr_data; + int len; + unsigned char count, value; + int i, j, k, c1,c2, z; + const char *headerToken; + STBI_NOTUSED(ri); + + // Check identifier + headerToken = stbi__hdr_gettoken(s,buffer); + if (strcmp(headerToken, "#?RADIANCE") != 0 && strcmp(headerToken, "#?RGBE") != 0) + return stbi__errpf("not HDR", "Corrupt HDR image"); + + // Parse header + for(;;) { + token = stbi__hdr_gettoken(s,buffer); + if (token[0] == 0) break; + if (strcmp(token, "FORMAT=32-bit_rle_rgbe") == 0) valid = 1; + } + + if (!valid) return stbi__errpf("unsupported format", "Unsupported HDR format"); + + // Parse width and height + // can't use sscanf() if we're not using stdio! + token = stbi__hdr_gettoken(s,buffer); + if (strncmp(token, "-Y ", 3)) return stbi__errpf("unsupported data layout", "Unsupported HDR format"); + token += 3; + height = (int) strtol(token, &token, 10); + while (*token == ' ') ++token; + if (strncmp(token, "+X ", 3)) return stbi__errpf("unsupported data layout", "Unsupported HDR format"); + token += 3; + width = (int) strtol(token, NULL, 10); + + if (height > STBI_MAX_DIMENSIONS) return stbi__errpf("too large","Very large image (corrupt?)"); + if (width > STBI_MAX_DIMENSIONS) return stbi__errpf("too large","Very large image (corrupt?)"); + + *x = width; + *y = height; + + if (comp) *comp = 3; + if (req_comp == 0) req_comp = 3; + + if (!stbi__mad4sizes_valid(width, height, req_comp, sizeof(float), 0)) + return stbi__errpf("too large", "HDR image is too large"); + + // Read data + hdr_data = (float *) stbi__malloc_mad4(width, height, req_comp, sizeof(float), 0); + if (!hdr_data) + return stbi__errpf("outofmem", "Out of memory"); + + // Load image data + // image data is stored as some number of sca + if ( width < 8 || width >= 32768) { + // Read flat data + for (j=0; j < height; ++j) { + for (i=0; i < width; ++i) { + stbi_uc rgbe[4]; + main_decode_loop: + stbi__getn(s, rgbe, 4); + stbi__hdr_convert(hdr_data + j * width * req_comp + i * req_comp, rgbe, req_comp); + } + } + } else { + // Read RLE-encoded data + scanline = NULL; + + for (j = 0; j < height; ++j) { + c1 = stbi__get8(s); + c2 = stbi__get8(s); + len = stbi__get8(s); + if (c1 != 2 || c2 != 2 || (len & 0x80)) { + // not run-length encoded, so we have to actually use THIS data as a decoded + // pixel (note this can't be a valid pixel--one of RGB must be >= 128) + stbi_uc rgbe[4]; + rgbe[0] = (stbi_uc) c1; + rgbe[1] = (stbi_uc) c2; + rgbe[2] = (stbi_uc) len; + rgbe[3] = (stbi_uc) stbi__get8(s); + stbi__hdr_convert(hdr_data, rgbe, req_comp); + i = 1; + j = 0; + STBI_FREE(scanline); + goto main_decode_loop; // yes, this makes no sense + } + len <<= 8; + len |= stbi__get8(s); + if (len != width) { STBI_FREE(hdr_data); STBI_FREE(scanline); return stbi__errpf("invalid decoded scanline length", "corrupt HDR"); } + if (scanline == NULL) { + scanline = (stbi_uc *) stbi__malloc_mad2(width, 4, 0); + if (!scanline) { + STBI_FREE(hdr_data); + return stbi__errpf("outofmem", "Out of memory"); + } + } + + for (k = 0; k < 4; ++k) { + int nleft; + i = 0; + while ((nleft = width - i) > 0) { + count = stbi__get8(s); + if (count > 128) { + // Run + value = stbi__get8(s); + count -= 128; + if (count > nleft) { STBI_FREE(hdr_data); STBI_FREE(scanline); return stbi__errpf("corrupt", "bad RLE data in HDR"); } + for (z = 0; z < count; ++z) + scanline[i++ * 4 + k] = value; + } else { + // Dump + if (count > nleft) { STBI_FREE(hdr_data); STBI_FREE(scanline); return stbi__errpf("corrupt", "bad RLE data in HDR"); } + for (z = 0; z < count; ++z) + scanline[i++ * 4 + k] = stbi__get8(s); + } + } + } + for (i=0; i < width; ++i) + stbi__hdr_convert(hdr_data+(j*width + i)*req_comp, scanline + i*4, req_comp); + } + if (scanline) + STBI_FREE(scanline); + } + + return hdr_data; +} + +static int stbi__hdr_info(stbi__context *s, int *x, int *y, int *comp) +{ + char buffer[STBI__HDR_BUFLEN]; + char *token; + int valid = 0; + int dummy; + + if (!x) x = &dummy; + if (!y) y = &dummy; + if (!comp) comp = &dummy; + + if (stbi__hdr_test(s) == 0) { + stbi__rewind( s ); + return 0; + } + + for(;;) { + token = stbi__hdr_gettoken(s,buffer); + if (token[0] == 0) break; + if (strcmp(token, "FORMAT=32-bit_rle_rgbe") == 0) valid = 1; + } + + if (!valid) { + stbi__rewind( s ); + return 0; + } + token = stbi__hdr_gettoken(s,buffer); + if (strncmp(token, "-Y ", 3)) { + stbi__rewind( s ); + return 0; + } + token += 3; + *y = (int) strtol(token, &token, 10); + while (*token == ' ') ++token; + if (strncmp(token, "+X ", 3)) { + stbi__rewind( s ); + return 0; + } + token += 3; + *x = (int) strtol(token, NULL, 10); + *comp = 3; + return 1; +} +#endif // STBI_NO_HDR + +#ifndef STBI_NO_BMP +static int stbi__bmp_info(stbi__context *s, int *x, int *y, int *comp) +{ + void *p; + stbi__bmp_data info; + + info.all_a = 255; + p = stbi__bmp_parse_header(s, &info); + if (p == NULL) { + stbi__rewind( s ); + return 0; + } + if (x) *x = s->img_x; + if (y) *y = s->img_y; + if (comp) { + if (info.bpp == 24 && info.ma == 0xff000000) + *comp = 3; + else + *comp = info.ma ? 4 : 3; + } + return 1; +} +#endif + +#ifndef STBI_NO_PSD +static int stbi__psd_info(stbi__context *s, int *x, int *y, int *comp) +{ + int channelCount, dummy, depth; + if (!x) x = &dummy; + if (!y) y = &dummy; + if (!comp) comp = &dummy; + if (stbi__get32be(s) != 0x38425053) { + stbi__rewind( s ); + return 0; + } + if (stbi__get16be(s) != 1) { + stbi__rewind( s ); + return 0; + } + stbi__skip(s, 6); + channelCount = stbi__get16be(s); + if (channelCount < 0 || channelCount > 16) { + stbi__rewind( s ); + return 0; + } + *y = stbi__get32be(s); + *x = stbi__get32be(s); + depth = stbi__get16be(s); + if (depth != 8 && depth != 16) { + stbi__rewind( s ); + return 0; + } + if (stbi__get16be(s) != 3) { + stbi__rewind( s ); + return 0; + } + *comp = 4; + return 1; +} + +static int stbi__psd_is16(stbi__context *s) +{ + int channelCount, depth; + if (stbi__get32be(s) != 0x38425053) { + stbi__rewind( s ); + return 0; + } + if (stbi__get16be(s) != 1) { + stbi__rewind( s ); + return 0; + } + stbi__skip(s, 6); + channelCount = stbi__get16be(s); + if (channelCount < 0 || channelCount > 16) { + stbi__rewind( s ); + return 0; + } + STBI_NOTUSED(stbi__get32be(s)); + STBI_NOTUSED(stbi__get32be(s)); + depth = stbi__get16be(s); + if (depth != 16) { + stbi__rewind( s ); + return 0; + } + return 1; +} +#endif + +#ifndef STBI_NO_PIC +static int stbi__pic_info(stbi__context *s, int *x, int *y, int *comp) +{ + int act_comp=0,num_packets=0,chained,dummy; + stbi__pic_packet packets[10]; + + if (!x) x = &dummy; + if (!y) y = &dummy; + if (!comp) comp = &dummy; + + if (!stbi__pic_is4(s,"\x53\x80\xF6\x34")) { + stbi__rewind(s); + return 0; + } + + stbi__skip(s, 88); + + *x = stbi__get16be(s); + *y = stbi__get16be(s); + if (stbi__at_eof(s)) { + stbi__rewind( s); + return 0; + } + if ( (*x) != 0 && (1 << 28) / (*x) < (*y)) { + stbi__rewind( s ); + return 0; + } + + stbi__skip(s, 8); + + do { + stbi__pic_packet *packet; + + if (num_packets==sizeof(packets)/sizeof(packets[0])) + return 0; + + packet = &packets[num_packets++]; + chained = stbi__get8(s); + packet->size = stbi__get8(s); + packet->type = stbi__get8(s); + packet->channel = stbi__get8(s); + act_comp |= packet->channel; + + if (stbi__at_eof(s)) { + stbi__rewind( s ); + return 0; + } + if (packet->size != 8) { + stbi__rewind( s ); + return 0; + } + } while (chained); + + *comp = (act_comp & 0x10 ? 4 : 3); + + return 1; +} +#endif + +// ************************************************************************************************* +// Portable Gray Map and Portable Pixel Map loader +// by Ken Miller +// +// PGM: http://netpbm.sourceforge.net/doc/pgm.html +// PPM: http://netpbm.sourceforge.net/doc/ppm.html +// +// Known limitations: +// Does not support comments in the header section +// Does not support ASCII image data (formats P2 and P3) + +#ifndef STBI_NO_PNM + +static int stbi__pnm_test(stbi__context *s) +{ + char p, t; + p = (char) stbi__get8(s); + t = (char) stbi__get8(s); + if (p != 'P' || (t != '5' && t != '6')) { + stbi__rewind( s ); + return 0; + } + return 1; +} + +static void *stbi__pnm_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri) +{ + stbi_uc *out; + STBI_NOTUSED(ri); + + ri->bits_per_channel = stbi__pnm_info(s, (int *)&s->img_x, (int *)&s->img_y, (int *)&s->img_n); + if (ri->bits_per_channel == 0) + return 0; + + if (s->img_y > STBI_MAX_DIMENSIONS) return stbi__errpuc("too large","Very large image (corrupt?)"); + if (s->img_x > STBI_MAX_DIMENSIONS) return stbi__errpuc("too large","Very large image (corrupt?)"); + + *x = s->img_x; + *y = s->img_y; + if (comp) *comp = s->img_n; + + if (!stbi__mad4sizes_valid(s->img_n, s->img_x, s->img_y, ri->bits_per_channel / 8, 0)) + return stbi__errpuc("too large", "PNM too large"); + + out = (stbi_uc *) stbi__malloc_mad4(s->img_n, s->img_x, s->img_y, ri->bits_per_channel / 8, 0); + if (!out) return stbi__errpuc("outofmem", "Out of memory"); + stbi__getn(s, out, s->img_n * s->img_x * s->img_y * (ri->bits_per_channel / 8)); + + if (req_comp && req_comp != s->img_n) { + out = stbi__convert_format(out, s->img_n, req_comp, s->img_x, s->img_y); + if (out == NULL) return out; // stbi__convert_format frees input on failure + } + return out; +} + +static int stbi__pnm_isspace(char c) +{ + return c == ' ' || c == '\t' || c == '\n' || c == '\v' || c == '\f' || c == '\r'; +} + +static void stbi__pnm_skip_whitespace(stbi__context *s, char *c) +{ + for (;;) { + while (!stbi__at_eof(s) && stbi__pnm_isspace(*c)) + *c = (char) stbi__get8(s); + + if (stbi__at_eof(s) || *c != '#') + break; + + while (!stbi__at_eof(s) && *c != '\n' && *c != '\r' ) + *c = (char) stbi__get8(s); + } +} + +static int stbi__pnm_isdigit(char c) +{ + return c >= '0' && c <= '9'; +} + +static int stbi__pnm_getinteger(stbi__context *s, char *c) +{ + int value = 0; + + while (!stbi__at_eof(s) && stbi__pnm_isdigit(*c)) { + value = value*10 + (*c - '0'); + *c = (char) stbi__get8(s); + } + + return value; +} + +static int stbi__pnm_info(stbi__context *s, int *x, int *y, int *comp) +{ + int maxv, dummy; + char c, p, t; + + if (!x) x = &dummy; + if (!y) y = &dummy; + if (!comp) comp = &dummy; + + stbi__rewind(s); + + // Get identifier + p = (char) stbi__get8(s); + t = (char) stbi__get8(s); + if (p != 'P' || (t != '5' && t != '6')) { + stbi__rewind(s); + return 0; + } + + *comp = (t == '6') ? 3 : 1; // '5' is 1-component .pgm; '6' is 3-component .ppm + + c = (char) stbi__get8(s); + stbi__pnm_skip_whitespace(s, &c); + + *x = stbi__pnm_getinteger(s, &c); // read width + stbi__pnm_skip_whitespace(s, &c); + + *y = stbi__pnm_getinteger(s, &c); // read height + stbi__pnm_skip_whitespace(s, &c); + + maxv = stbi__pnm_getinteger(s, &c); // read max value + if (maxv > 65535) + return stbi__err("max value > 65535", "PPM image supports only 8-bit and 16-bit images"); + else if (maxv > 255) + return 16; + else + return 8; +} + +static int stbi__pnm_is16(stbi__context *s) +{ + if (stbi__pnm_info(s, NULL, NULL, NULL) == 16) + return 1; + return 0; +} +#endif + +static int stbi__info_main(stbi__context *s, int *x, int *y, int *comp) +{ + #ifndef STBI_NO_JPEG + if (stbi__jpeg_info(s, x, y, comp)) return 1; + #endif + + #ifndef STBI_NO_PNG + if (stbi__png_info(s, x, y, comp)) return 1; + #endif + + #ifndef STBI_NO_GIF + if (stbi__gif_info(s, x, y, comp)) return 1; + #endif + + #ifndef STBI_NO_BMP + if (stbi__bmp_info(s, x, y, comp)) return 1; + #endif + + #ifndef STBI_NO_PSD + if (stbi__psd_info(s, x, y, comp)) return 1; + #endif + + #ifndef STBI_NO_PIC + if (stbi__pic_info(s, x, y, comp)) return 1; + #endif + + #ifndef STBI_NO_PNM + if (stbi__pnm_info(s, x, y, comp)) return 1; + #endif + + #ifndef STBI_NO_HDR + if (stbi__hdr_info(s, x, y, comp)) return 1; + #endif + + // test tga last because it's a crappy test! + #ifndef STBI_NO_TGA + if (stbi__tga_info(s, x, y, comp)) + return 1; + #endif + return stbi__err("unknown image type", "Image not of any known type, or corrupt"); +} + +static int stbi__is_16_main(stbi__context *s) +{ + #ifndef STBI_NO_PNG + if (stbi__png_is16(s)) return 1; + #endif + + #ifndef STBI_NO_PSD + if (stbi__psd_is16(s)) return 1; + #endif + + #ifndef STBI_NO_PNM + if (stbi__pnm_is16(s)) return 1; + #endif + return 0; +} + +#ifndef STBI_NO_STDIO +STBIDEF int stbi_info(char const *filename, int *x, int *y, int *comp) +{ + FILE *f = stbi__fopen(filename, "rb"); + int result; + if (!f) return stbi__err("can't fopen", "Unable to open file"); + result = stbi_info_from_file(f, x, y, comp); + fclose(f); + return result; +} + +STBIDEF int stbi_info_from_file(FILE *f, int *x, int *y, int *comp) +{ + int r; + stbi__context s; + long pos = ftell(f); + stbi__start_file(&s, f); + r = stbi__info_main(&s,x,y,comp); + fseek(f,pos,SEEK_SET); + return r; +} + +STBIDEF int stbi_is_16_bit(char const *filename) +{ + FILE *f = stbi__fopen(filename, "rb"); + int result; + if (!f) return stbi__err("can't fopen", "Unable to open file"); + result = stbi_is_16_bit_from_file(f); + fclose(f); + return result; +} + +STBIDEF int stbi_is_16_bit_from_file(FILE *f) +{ + int r; + stbi__context s; + long pos = ftell(f); + stbi__start_file(&s, f); + r = stbi__is_16_main(&s); + fseek(f,pos,SEEK_SET); + return r; +} +#endif // !STBI_NO_STDIO + +STBIDEF int stbi_info_from_memory(stbi_uc const *buffer, int len, int *x, int *y, int *comp) +{ + stbi__context s; + stbi__start_mem(&s,buffer,len); + return stbi__info_main(&s,x,y,comp); +} + +STBIDEF int stbi_info_from_callbacks(stbi_io_callbacks const *c, void *user, int *x, int *y, int *comp) +{ + stbi__context s; + stbi__start_callbacks(&s, (stbi_io_callbacks *) c, user); + return stbi__info_main(&s,x,y,comp); +} + +STBIDEF int stbi_is_16_bit_from_memory(stbi_uc const *buffer, int len) +{ + stbi__context s; + stbi__start_mem(&s,buffer,len); + return stbi__is_16_main(&s); +} + +STBIDEF int stbi_is_16_bit_from_callbacks(stbi_io_callbacks const *c, void *user) +{ + stbi__context s; + stbi__start_callbacks(&s, (stbi_io_callbacks *) c, user); + return stbi__is_16_main(&s); +} + +#endif // STB_IMAGE_IMPLEMENTATION + +/* + revision history: + 2.20 (2019-02-07) support utf8 filenames in Windows; fix warnings and platform ifdefs + 2.19 (2018-02-11) fix warning + 2.18 (2018-01-30) fix warnings + 2.17 (2018-01-29) change sbti__shiftsigned to avoid clang -O2 bug + 1-bit BMP + *_is_16_bit api + avoid warnings + 2.16 (2017-07-23) all functions have 16-bit variants; + STBI_NO_STDIO works again; + compilation fixes; + fix rounding in unpremultiply; + optimize vertical flip; + disable raw_len validation; + documentation fixes + 2.15 (2017-03-18) fix png-1,2,4 bug; now all Imagenet JPGs decode; + warning fixes; disable run-time SSE detection on gcc; + uniform handling of optional "return" values; + thread-safe initialization of zlib tables + 2.14 (2017-03-03) remove deprecated STBI_JPEG_OLD; fixes for Imagenet JPGs + 2.13 (2016-11-29) add 16-bit API, only supported for PNG right now + 2.12 (2016-04-02) fix typo in 2.11 PSD fix that caused crashes + 2.11 (2016-04-02) allocate large structures on the stack + remove white matting for transparent PSD + fix reported channel count for PNG & BMP + re-enable SSE2 in non-gcc 64-bit + support RGB-formatted JPEG + read 16-bit PNGs (only as 8-bit) + 2.10 (2016-01-22) avoid warning introduced in 2.09 by STBI_REALLOC_SIZED + 2.09 (2016-01-16) allow comments in PNM files + 16-bit-per-pixel TGA (not bit-per-component) + info() for TGA could break due to .hdr handling + info() for BMP to shares code instead of sloppy parse + can use STBI_REALLOC_SIZED if allocator doesn't support realloc + code cleanup + 2.08 (2015-09-13) fix to 2.07 cleanup, reading RGB PSD as RGBA + 2.07 (2015-09-13) fix compiler warnings + partial animated GIF support + limited 16-bpc PSD support + #ifdef unused functions + bug with < 92 byte PIC,PNM,HDR,TGA + 2.06 (2015-04-19) fix bug where PSD returns wrong '*comp' value + 2.05 (2015-04-19) fix bug in progressive JPEG handling, fix warning + 2.04 (2015-04-15) try to re-enable SIMD on MinGW 64-bit + 2.03 (2015-04-12) extra corruption checking (mmozeiko) + stbi_set_flip_vertically_on_load (nguillemot) + fix NEON support; fix mingw support + 2.02 (2015-01-19) fix incorrect assert, fix warning + 2.01 (2015-01-17) fix various warnings; suppress SIMD on gcc 32-bit without -msse2 + 2.00b (2014-12-25) fix STBI_MALLOC in progressive JPEG + 2.00 (2014-12-25) optimize JPG, including x86 SSE2 & NEON SIMD (ryg) + progressive JPEG (stb) + PGM/PPM support (Ken Miller) + STBI_MALLOC,STBI_REALLOC,STBI_FREE + GIF bugfix -- seemingly never worked + STBI_NO_*, STBI_ONLY_* + 1.48 (2014-12-14) fix incorrectly-named assert() + 1.47 (2014-12-14) 1/2/4-bit PNG support, both direct and paletted (Omar Cornut & stb) + optimize PNG (ryg) + fix bug in interlaced PNG with user-specified channel count (stb) + 1.46 (2014-08-26) + fix broken tRNS chunk (colorkey-style transparency) in non-paletted PNG + 1.45 (2014-08-16) + fix MSVC-ARM internal compiler error by wrapping malloc + 1.44 (2014-08-07) + various warning fixes from Ronny Chevalier + 1.43 (2014-07-15) + fix MSVC-only compiler problem in code changed in 1.42 + 1.42 (2014-07-09) + don't define _CRT_SECURE_NO_WARNINGS (affects user code) + fixes to stbi__cleanup_jpeg path + added STBI_ASSERT to avoid requiring assert.h + 1.41 (2014-06-25) + fix search&replace from 1.36 that messed up comments/error messages + 1.40 (2014-06-22) + fix gcc struct-initialization warning + 1.39 (2014-06-15) + fix to TGA optimization when req_comp != number of components in TGA; + fix to GIF loading because BMP wasn't rewinding (whoops, no GIFs in my test suite) + add support for BMP version 5 (more ignored fields) + 1.38 (2014-06-06) + suppress MSVC warnings on integer casts truncating values + fix accidental rename of 'skip' field of I/O + 1.37 (2014-06-04) + remove duplicate typedef + 1.36 (2014-06-03) + convert to header file single-file library + if de-iphone isn't set, load iphone images color-swapped instead of returning NULL + 1.35 (2014-05-27) + various warnings + fix broken STBI_SIMD path + fix bug where stbi_load_from_file no longer left file pointer in correct place + fix broken non-easy path for 32-bit BMP (possibly never used) + TGA optimization by Arseny Kapoulkine + 1.34 (unknown) + use STBI_NOTUSED in stbi__resample_row_generic(), fix one more leak in tga failure case + 1.33 (2011-07-14) + make stbi_is_hdr work in STBI_NO_HDR (as specified), minor compiler-friendly improvements + 1.32 (2011-07-13) + support for "info" function for all supported filetypes (SpartanJ) + 1.31 (2011-06-20) + a few more leak fixes, bug in PNG handling (SpartanJ) + 1.30 (2011-06-11) + added ability to load files via callbacks to accomidate custom input streams (Ben Wenger) + removed deprecated format-specific test/load functions + removed support for installable file formats (stbi_loader) -- would have been broken for IO callbacks anyway + error cases in bmp and tga give messages and don't leak (Raymond Barbiero, grisha) + fix inefficiency in decoding 32-bit BMP (David Woo) + 1.29 (2010-08-16) + various warning fixes from Aurelien Pocheville + 1.28 (2010-08-01) + fix bug in GIF palette transparency (SpartanJ) + 1.27 (2010-08-01) + cast-to-stbi_uc to fix warnings + 1.26 (2010-07-24) + fix bug in file buffering for PNG reported by SpartanJ + 1.25 (2010-07-17) + refix trans_data warning (Won Chun) + 1.24 (2010-07-12) + perf improvements reading from files on platforms with lock-heavy fgetc() + minor perf improvements for jpeg + deprecated type-specific functions so we'll get feedback if they're needed + attempt to fix trans_data warning (Won Chun) + 1.23 fixed bug in iPhone support + 1.22 (2010-07-10) + removed image *writing* support + stbi_info support from Jetro Lauha + GIF support from Jean-Marc Lienher + iPhone PNG-extensions from James Brown + warning-fixes from Nicolas Schulz and Janez Zemva (i.stbi__err. Janez (U+017D)emva) + 1.21 fix use of 'stbi_uc' in header (reported by jon blow) + 1.20 added support for Softimage PIC, by Tom Seddon + 1.19 bug in interlaced PNG corruption check (found by ryg) + 1.18 (2008-08-02) + fix a threading bug (local mutable static) + 1.17 support interlaced PNG + 1.16 major bugfix - stbi__convert_format converted one too many pixels + 1.15 initialize some fields for thread safety + 1.14 fix threadsafe conversion bug + header-file-only version (#define STBI_HEADER_FILE_ONLY before including) + 1.13 threadsafe + 1.12 const qualifiers in the API + 1.11 Support installable IDCT, colorspace conversion routines + 1.10 Fixes for 64-bit (don't use "unsigned long") + optimized upsampling by Fabian "ryg" Giesen + 1.09 Fix format-conversion for PSD code (bad global variables!) + 1.08 Thatcher Ulrich's PSD code integrated by Nicolas Schulz + 1.07 attempt to fix C++ warning/errors again + 1.06 attempt to fix C++ warning/errors again + 1.05 fix TGA loading to return correct *comp and use good luminance calc + 1.04 default float alpha is 1, not 255; use 'void *' for stbi_image_free + 1.03 bugfixes to STBI_NO_STDIO, STBI_NO_HDR + 1.02 support for (subset of) HDR files, float interface for preferred access to them + 1.01 fix bug: possible bug in handling right-side up bmps... not sure + fix bug: the stbi__bmp_load() and stbi__tga_load() functions didn't work at all + 1.00 interface to zlib that skips zlib header + 0.99 correct handling of alpha in palette + 0.98 TGA loader by lonesock; dynamically add loaders (untested) + 0.97 jpeg errors on too large a file; also catch another malloc failure + 0.96 fix detection of invalid v value - particleman@mollyrocket forum + 0.95 during header scan, seek to markers in case of padding + 0.94 STBI_NO_STDIO to disable stdio usage; rename all #defines the same + 0.93 handle jpegtran output; verbose errors + 0.92 read 4,8,16,24,32-bit BMP files of several formats + 0.91 output 24-bit Windows 3.0 BMP files + 0.90 fix a few more warnings; bump version number to approach 1.0 + 0.61 bugfixes due to Marc LeBlanc, Christopher Lloyd + 0.60 fix compiling as c++ + 0.59 fix warnings: merge Dave Moore's -Wall fixes + 0.58 fix bug: zlib uncompressed mode len/nlen was wrong endian + 0.57 fix bug: jpg last huffman symbol before marker was >9 bits but less than 16 available + 0.56 fix bug: zlib uncompressed mode len vs. nlen + 0.55 fix bug: restart_interval not initialized to 0 + 0.54 allow NULL for 'int *comp' + 0.53 fix bug in png 3->4; speedup png decoding + 0.52 png handles req_comp=3,4 directly; minor cleanup; jpeg comments + 0.51 obey req_comp requests, 1-component jpegs return as 1-component, + on 'test' only check type, not whether we support this variant + 0.50 (2006-11-19) + first released version +*/ + + +/* +------------------------------------------------------------------------------ +This software is available under 2 licenses -- choose whichever you prefer. +------------------------------------------------------------------------------ +ALTERNATIVE A - MIT License +Copyright (c) 2017 Sean Barrett +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +------------------------------------------------------------------------------ +ALTERNATIVE B - Public Domain (www.unlicense.org) +This is free and unencumbered software released into the public domain. +Anyone is free to copy, modify, publish, use, compile, sell, or distribute this +software, either in source code form or as a compiled binary, for any purpose, +commercial or non-commercial, and by any means. +In jurisdictions that recognize copyright laws, the author or authors of this +software dedicate any and all copyright interest in the software to the public +domain. We make this dedication for the benefit of the public at large and to +the detriment of our heirs and successors. We intend this dedication to be an +overt act of relinquishment in perpetuity of all present and future rights to +this software under copyright law. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN +ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +------------------------------------------------------------------------------ +*/ diff --git a/projs/shadow/shadow-engine/shadow-utility/src/string-helpers.h b/projs/shadow/shadow-engine/shadow-utility/inc/string-helpers.h similarity index 100% rename from projs/shadow/shadow-engine/shadow-utility/src/string-helpers.h rename to projs/shadow/shadow-engine/shadow-utility/inc/string-helpers.h diff --git a/projs/shadow/shadow-engine/shadow-utility/src/File.cpp b/projs/shadow/shadow-engine/shadow-utility/src/File.cpp new file mode 100644 index 0000000..cf20d6a --- /dev/null +++ b/projs/shadow/shadow-engine/shadow-utility/src/File.cpp @@ -0,0 +1,26 @@ +#include +#include +#include +namespace shadowutil { + FileData* loadFile(std::string path) { + // Verify the file + std::ifstream file(path, std::ios::binary | std::ios::ate); + + if (!file.is_open()) + throw std::runtime_error("Unable to open specified file: " + std::string(path)); + + auto* data = new FileData {}; + + // Read the file's size (opened with At The End, so just tellg the size) + size_t size = file.tellg(); + data->data.resize(size); + data->size = size; + // Go to the front of the file + file.seekg(0); + // Read the file into the buffer + file.read(data->data.data(), size); + file.close(); + + return data; + } +} \ No newline at end of file diff --git a/projs/shadow/shadow-engine/shadow-utility/src/string-helpers.cpp b/projs/shadow/shadow-engine/shadow-utility/src/string-helpers.cpp index 8c0ae0e..d7a82f6 100644 --- a/projs/shadow/shadow-engine/shadow-utility/src/string-helpers.cpp +++ b/projs/shadow/shadow-engine/shadow-utility/src/string-helpers.cpp @@ -1,5 +1,5 @@ -#include "string-helpers.h" +#include "../inc/string-helpers.h" std::vector explode(const std::string& s, const char& c) { diff --git a/projs/shadow/shadow-runtime/CMakeLists.txt b/projs/shadow/shadow-runtime/CMakeLists.txt index 8f953ee..a70601b 100644 --- a/projs/shadow/shadow-runtime/CMakeLists.txt +++ b/projs/shadow/shadow-runtime/CMakeLists.txt @@ -8,7 +8,6 @@ add_executable(shadow-runtime ${SOURCES}) target_include_directories(shadow-runtime PRIVATE ${SDL2_INCLUDE_DIRS}) target_link_libraries(shadow-runtime PRIVATE SDL2::SDL2main PUBLIC shadow-engine) - add_custom_command(TARGET shadow-runtime POST_BUILD COMMAND ${CMAKE_COMMAND} -E copy $ $ COMMAND_EXPAND_LISTS diff --git a/projs/test-game/inc/GameModule.h b/projs/test-game/inc/GameModule.h index 2706956..b3430f7 100644 --- a/projs/test-game/inc/GameModule.h +++ b/projs/test-game/inc/GameModule.h @@ -13,13 +13,17 @@ public: void Init() override; - void Update() override; + void Update(int frame) override; void PreRender() override; - void Render() override; + void Recreate() override; - void LateRender() override; + void OverlayRender() override; + + void Render(VkCommandBuffer& commands, int frame) override; + + void LateRender(VkCommandBuffer& commands, int frame) override; void AfterFrameEnd() override; diff --git a/projs/test-game/src/GameModule.cpp b/projs/test-game/src/GameModule.cpp index 517ad27..0e78683 100644 --- a/projs/test-game/src/GameModule.cpp +++ b/projs/test-game/src/GameModule.cpp @@ -1,171 +1,92 @@ #include #include "imgui.h" #include "spdlog/spdlog.h" -#include "vlkx/render/geometry/SingleRenderer.h" #include "imgui_impl_vulkan.h" -#include "core/ShadowApplication.h" -#include "core/SDL2Module.h" #include "imgui_impl_sdl.h" #include "core/Time.h" - +#include "vlkx/render/Camera.h" +#include "vlkx/vulkan/abstraction/Buffer.h" +#include "vlkx/render/render_pass/ScreenRenderPass.h" +#include "temp/model/Builder.h" +#include "core/ModuleManager.h" #define CATCH(x) \ try { x } catch (std::exception& e) { spdlog::error(e.what()); exit(0); } -// Create the renderer -SingleRenderer object; - -// Create the camera -Camera camera; - SHObject_Base_Impl(GameModule) -void GameModule::PreInit() { spdlog::info("Game Module loading.."); } +struct Transformation { + alignas(sizeof(glm::mat4)) glm::mat4 proj_view_model; +}; + +std::unique_ptr trans_constant_; +std::unique_ptr cube_model_; +float aspectRatio; + +void GameModule::PreInit() { } void GameModule::Init() { + spdlog::info("Game Module loading level.."); + trans_constant_ = std::make_unique( + sizeof(Transformation), 2); - auto shApp = ShadowEngine::ShadowApplication::Get(); + auto extent = ShadowEngine::ModuleManager::getInstance()->renderer->GetRenderExtent(); + aspectRatio = (float) extent.width / extent.height; - ShadowEngine::ModuleManager &moduleManager = shApp.GetModuleManager(); + /* Model */ + cube_model_ = vlkxtemp::ModelBuilder { + "Walrus", 2, aspectRatio, + vlkxtemp::ModelBuilder::SingleMeshModel {"resources/walrus/walrus.obj", 1, + {{ vlkxtemp::ModelBuilder::TextureType::Diffuse, { { "resources/walrus/texture.png" } } } } + }} + .bindTextures(vlkxtemp::ModelBuilder::TextureType::Diffuse, 1) + .pushStage(VK_SHADER_STAGE_VERTEX_BIT) + .pushConstant(trans_constant_.get(), 0) + .shader(VK_SHADER_STAGE_VERTEX_BIT, "resources/walrus/cube.vert.spv") + .shader(VK_SHADER_STAGE_FRAGMENT_BIT, "resources/walrus/cube.frag.spv") + .build(); - auto sdl2module = moduleManager.GetModuleByType(); - - CATCH(VulkanManager::getInstance()->initVulkan(sdl2module->_window->sdlWindowPtr);) - - IMGUI_CHECKVERSION(); - ImGui::CreateContext(); - ImGuiIO& io = ImGui::GetIO(); (void)io; - io.ConfigFlags |= ImGuiConfigFlags_DockingEnable; // Enable Docking - io.ConfigFlags |= ImGuiConfigFlags_ViewportsEnable; // Enable Multi-Viewport / Platform Windows - - // Setup Dear ImGui style - ImGui::StyleColorsDark(); - VkDescriptorPool imGuiPool; - VulkanManager* vk = VulkanManager::getInstance(); - VkDescriptorPoolSize pool_sizes[] = - { - { VK_DESCRIPTOR_TYPE_SAMPLER, 1000 }, - { VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 1000 }, - { VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, 1000 }, - { VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, 1000 }, - { VK_DESCRIPTOR_TYPE_UNIFORM_TEXEL_BUFFER, 1000 }, - { VK_DESCRIPTOR_TYPE_STORAGE_TEXEL_BUFFER, 1000 }, - { VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 1000 }, - { VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 1000 }, - { VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC, 1000 }, - { VK_DESCRIPTOR_TYPE_STORAGE_BUFFER_DYNAMIC, 1000 }, - { VK_DESCRIPTOR_TYPE_INPUT_ATTACHMENT, 1000 } - }; - - VkDescriptorPoolCreateInfo pool_info = {}; - pool_info.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO; - pool_info.flags = VK_DESCRIPTOR_POOL_CREATE_FREE_DESCRIPTOR_SET_BIT; - pool_info.maxSets = 1000 * IM_ARRAYSIZE(pool_sizes); - pool_info.poolSizeCount = (uint32_t)IM_ARRAYSIZE(pool_sizes); - pool_info.pPoolSizes = pool_sizes; - vkCreateDescriptorPool(vk->getDevice()->logical, &pool_info, VK_NULL_HANDLE, &imGuiPool); - - // Setup Platform/Renderer backends - ImGui_ImplSDL2_InitForVulkan(sdl2module->_window->sdlWindowPtr); - ImGui_ImplVulkan_InitInfo init_info = {}; - init_info.Instance = vk->getVulkan(); - init_info.PhysicalDevice = vk->getDevice()->physical; - init_info.Device = vk->getDevice()->logical; - init_info.QueueFamily = vk->getDevice()->queueData.graphics; - init_info.Queue = vk->getDevice()->graphicsQueue; - init_info.PipelineCache = VK_NULL_HANDLE; - init_info.DescriptorPool = imGuiPool; - init_info.Subpass = 0; - init_info.MinImageCount = vk->getSwapchain()->images.size(); - init_info.ImageCount = vk->getSwapchain()->images.size(); - init_info.MSAASamples = VK_SAMPLE_COUNT_1_BIT; - init_info.Allocator = VK_NULL_HANDLE; - init_info.CheckVkResultFn = nullptr; - ImGui_ImplVulkan_Init(&init_info, vk->getRenderPass()->pass); - - // Upload Fonts - { - // Prepare to create a temporary command pool. - VkCommandPool pool; - VkCommandPoolCreateInfo poolCreateInfo = {}; - poolCreateInfo.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO; - poolCreateInfo.queueFamilyIndex = vk->getDevice()->queueData.graphics; - poolCreateInfo.flags = 0; - - // Create the pool - if (vkCreateCommandPool(vk->getDevice()->logical, &poolCreateInfo, nullptr, &pool) != VK_SUCCESS) - throw std::runtime_error("Unable to allocate a temporary command pool"); - - VkCommandBuffer buffer = VkTools::createTempCommandBuffer(pool, vk->getDevice()->logical); - - ImGui_ImplVulkan_CreateFontsTexture(buffer); - - VkSubmitInfo end_info = {}; - end_info.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO; - end_info.commandBufferCount = 1; - end_info.pCommandBuffers = &buffer; - - VkTools::executeAndDeleteTempBuffer(buffer, pool, vk->getDevice()->graphicsQueue, vk->getDevice()->logical); - - ImGui_ImplVulkan_DestroyFontUploadObjects(); - } - - CATCH(object.createSingleRenderer(Geo::MeshType::Cube, glm::vec3(-1, 0, -1), glm::vec3(0.5));) - camera.init(45, 1280, 720, 0.1, 10000); - camera.setPosition(glm::vec3(0, 0, 4)); + Recreate(); } -void GameModule::Update() { - object.setRotation(glm::rotate(object.getRotation(), (float) 0.5, glm::vec3(1, 0, 0))); +void GameModule::Recreate() { + auto extent = ShadowEngine::ModuleManager::getInstance()->renderer->GetRenderExtent(); + cube_model_->update(true, extent, VK_SAMPLE_COUNT_1_BIT, *VulkanModule::getInstance()->getRenderPass()->getPass(), 0); } -void GameModule::PreRender() { - ImGui_ImplVulkan_NewFrame(); - ImGui_ImplSDL2_NewFrame(); - ImGui::NewFrame(); +void GameModule::Update(int frame) { + const float elapsed_time = Time::timeSinceStart; + const glm::mat4 model = glm::rotate(glm::mat4{1.0f}, + (elapsed_time / 1000 / 2) * glm::radians(90.0f), + glm::vec3{1.0f, 1.0f, 0.0f}); + const glm::mat4 view = glm::lookAt(glm::vec3{3.0f}, glm::vec3{0.0f}, + glm::vec3{0.0f, 0.0f, 1.0f}); + const glm::mat4 proj = glm::perspective( + glm::radians(45.0f), aspectRatio, + 0.1f, 100.0f); + *trans_constant_->getData(frame) = {proj * view * model}; } -void GameModule::Render() { +void GameModule::Render(VkCommandBuffer& commands, int frame) { + cube_model_->draw(commands, frame, 1); +} + +void GameModule::OverlayRender() { bool active = true; - ImGui::Begin("Game module window", &active, ImGuiWindowFlags_MenuBar); - - ImGui::Text("Such teext from curle's branch"); - + if (ImGui::Begin("Game module window", &active, ImGuiWindowFlags_MenuBar)) + ImGui::Text("Such text from curle's branch"); ImGui::End(); - CATCH(object.updateUniforms(camera);) - CATCH(object.draw();) - - bool showDemo = true; - if (showDemo) - ImGui::ShowDemoWindow(&showDemo); - -} - -void GameModule::LateRender() { - ImGui::Render(); - ImGuiIO& io = ImGui::GetIO(); (void)io; - - ImGui_ImplVulkan_RenderDrawData(ImGui::GetDrawData(), VulkanManager::getInstance()->getCurrentCommandBuffer()); - - // Update and Render additional Platform Windows - if (io.ConfigFlags & ImGuiConfigFlags_ViewportsEnable) - { - ImGui::UpdatePlatformWindows(); - ImGui::RenderPlatformWindowsDefault(); - } + bool open = false; + ImGui::ShowDemoWindow(&open); } void GameModule::AfterFrameEnd() { Time::UpdateTime(); } -void GameModule::Destroy() { - ImGui_ImplVulkan_Shutdown(); - ImGui_ImplSDL2_Shutdown(); - ImGui::DestroyContext(); -} +void GameModule::LateRender(VkCommandBuffer& commands, int frame) {} +void GameModule::PreRender() {} -void GameModule::Event(SDL_Event *) { - -} \ No newline at end of file +void GameModule::Destroy() {} +void GameModule::Event(SDL_Event *) {} \ No newline at end of file diff --git a/projs/test-game/src/entry.cpp b/projs/test-game/src/entry.cpp index 1db046f..f36f5b0 100644 --- a/projs/test-game/src/entry.cpp +++ b/projs/test-game/src/entry.cpp @@ -7,19 +7,8 @@ #include "core/ShadowApplication.h" #include "GameModule.h" #include "core/ShadowApplication.h" -#include "vlkx/vulkan/VulkanManager.h" +#include "vlkx/vulkan/VulkanModule.h" extern "C" __declspec(dllexport) void shadow_main(ShadowEngine::ShadowApplication* app) { - - std::cout << "HIIII from a loaded dll weeeeeee!!!" << std::endl; - app->GetModuleManager().PushModule(std::make_shared(), "game"); - - if(app == &ShadowEngine::ShadowApplication::Get()){ - std::cout << "They are the same!!!" << std::endl; - } - - printf("dll side: %p \n", VulkanManager::getInstance()); - printf("dll next ID: %llu \n", ShadowEngine::SHObject::GenerateId()); - } diff --git a/resources/planets/skybox.frag b/resources/planets/skybox.frag new file mode 100644 index 0000000..693740c --- /dev/null +++ b/resources/planets/skybox.frag @@ -0,0 +1,10 @@ +#version 460 core + +layout(binding = 1) uniform samplerCube tex; + +layout(location = 0) in vec3 tex_coord; +layout(location = 0) out vec4 frag_color; + +void main() { + frag_color = texture(tex, tex_coord); +} \ No newline at end of file diff --git a/resources/planets/skybox.vert b/resources/planets/skybox.vert new file mode 100644 index 0000000..c42a46c --- /dev/null +++ b/resources/planets/skybox.vert @@ -0,0 +1,18 @@ +#version 450 + +layout(std140, push_constant) uniform Transform { + mat4 matrix; +} transf; + +layout(location = 0) in vec3 in_pos; +layout(location = 1) in vec3 in_normal; +layout(location = 2) in vec2 in_uv; + +layout(location = 0) out vec3 texCoord; + +void main() { + + gl_Position = transf.matrix * vec4(in_pos, 1.0); + gl_Position.zw = vec2(1.0); + texCoord = in_pos; +} \ No newline at end of file diff --git a/resources/tri/tri.frag b/resources/tri/tri.frag new file mode 100644 index 0000000..8935b06 --- /dev/null +++ b/resources/tri/tri.frag @@ -0,0 +1,13 @@ +#version 450 + +layout(push_constant) uniform Alpha { + float value; +} alpha; + +layout(location = 0) in vec3 color; + +layout(location = 0) out vec4 frag_color; + +void main() { + frag_color = vec4(color, alpha.value); +} \ No newline at end of file diff --git a/resources/tri/tri.frag.spv b/resources/tri/tri.frag.spv new file mode 100644 index 0000000000000000000000000000000000000000..013bd9290420dfb4d9de1cf25ca5bbee9f9dfe06 GIT binary patch literal 764 zcmYk3TT22_6ot2rw=_*NwR#Z4$3S|h2%^A;m>vX+e!wuO)xf0U4f=cis$PQDcO1b3 zYxY^|tg|m?ymU8jma?Li?cBDjW@#yACCjuVnqxn2ouD;_*5J%HVFpd_J(JI%mUXq?7vV=Rn#Hq)Ny)fg66$zGee~n`XQ-Hy0%vW)c&)O|Q&kKT zkFOle)D~E9=G*b-y&PTVz~i~R%v{&x#0&Qd=JrkPbJ99OOZj*sIP-M$EhzHJ==TYy z2Q>UeMf`C5CB>A0jvr2sXl=Qeaa%8=f42QcXn5hwOa6v#@J67f+GA5;W!mt}XEeF(%w@O6;MAPwV8$c&;s`P=+e zzAWtddZt4L6 ziA(#Kz+0H%*(^>UMze%KjM<)nW^Jp-T^d~_lbd8xCQovORya1Xys)LsGoka2&mA0Y zv+~-$s53cLjF}btncDF$Ta>q1oJ?bEfY?KtPGf=Y$=x~3#|~^3jq{@N*+-nG`NYKo zw;RPhWd10NJ8-=ASoI#fl$r9F4<5649yS=e2^GPH7uNIa%I4rR&#cc|;nSn5Eb{hM zgPPbqS##2T$(Hi<;W(dOoAMW=`x4G!?eld|Z(IJ7H2Gk=vY5@+AcpkC`zY=2aZ&s1 z6^uSBj#t&C5B{oz!(409^np*kjx;lXQxBiF0V5yohQyfH@9;m%BA40lzt+Cb$7gqN zd_8tYy)(TbyJ9CCIPXFN=}V#1=t}7SS17$VYfNg2^=gcop0`zF)W6bn%=bw`K78Wb h^Uhc6QL#%{i85`6f64&Iu%iTQ8gpKft3`45Y(Q|U#bKb053`8`|A zgU#;Do430YuXfQi+p)Io+oAoPfz`yAb*$lX5I%;Jb(u^qucj!v<~bpno;9tmOuWvs z1sA)Dfub)RyauT9)Ya@ukLsEhgi$b?1>r*!uU65z{LIrR&lYKvWJMa6*?WqvRncXO zIDbp30$+;iT;F^*`?;9EMDa3T7AD4%GqW=NQ0=?DRO@mQ+GR}MRUi}983#4@9+pg))%2MXRl UNrk7O0uB7VI^YfNM-y%ozf_zuQvd(} literal 0 HcmV?d00001 diff --git a/resources/walrus/cube.vert b/resources/walrus/cube.vert new file mode 100644 index 0000000..ea2110f --- /dev/null +++ b/resources/walrus/cube.vert @@ -0,0 +1,16 @@ +#version 450 + +layout(std140, push_constant) uniform Transform { + mat4 matrix; +} transf; + +layout(location = 0) in vec3 in_pos; +layout(location = 1) in vec3 in_normal; +layout(location = 2) in vec2 in_uv; + +layout(location = 0) out vec2 texCoord; + +void main() { + gl_Position = transf.matrix * vec4(in_pos, 1.0); + texCoord = in_uv; +} \ No newline at end of file diff --git a/resources/walrus/cube.vert.spv b/resources/walrus/cube.vert.spv new file mode 100644 index 0000000000000000000000000000000000000000..accf4a648685c75d45b724f995dc5f7d2d793a8f GIT binary patch literal 1392 zcmY+C-EPxB5QVo%ofb-4O6depR?7JQVhY1EH;7OZJxzE$U1#2!oT8VDK>p$A zW9>T)M|>QWxmAVqu_?c-bhXd?Y#L6oqRzuC)y`3t1E}6)tu$0x+zM; z9ZSVLSk5}G{=R(qe>!?aM_*3I9*#K&im*p_EQa1#o`Bx@{fhmc(<3w21#+MxN1z<^ z*%es$^f~X-Bfit~mhh>2Q!IQ>IjNKVwssv^U)WJT?!~A6E&VUc`U2}|<6qI9e0%bF z&FJB}+R?AeK@9lPU6XaQdENMYcR21@t8{f1$n8ft5eJbj@&n7xgU<) z_`DA|;y0T;dV1WErDpCXzAL*Rz;hm-Im1y4+B4f7=aK$bW> z`SAHpaKsm66lwCy^)eWw*`27`noO9 z(1@1;eeLdOx0^geY-l_w9 VU!XQ-@K~UKV$2Euk0$g=_zRW?X~_Tp literal 0 HcmV?d00001 diff --git a/resources/walrus/texture.png b/resources/walrus/texture.png new file mode 100644 index 0000000000000000000000000000000000000000..7e6ef55c638af38f18b5acb0a4f0dd07eed7c773 GIT binary patch literal 499093 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%0DUju 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%0Dg^-hW$a}HX6hyArA&uyc(^4iJ) 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 O0000bKNy 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 literal 0 HcmV?d00001 diff --git a/vlkx-resources/pkg/index.vxi b/vlkx-resources/pkg/index.vxi new file mode 100644 index 0000000..500af1f --- /dev/null +++ b/vlkx-resources/pkg/index.vxi @@ -0,0 +1 @@ +01.vxp \ No newline at end of file diff --git a/vlkx-resources/shader/SPIRV/basic.frag.spv b/vlkx-resources/shader/SPIRV/basic.frag.spv new file mode 100644 index 0000000000000000000000000000000000000000..0e7d70f131c8a07a5be16e4e79509063939ab125 GIT binary patch literal 536 zcmYk1%SyvQ6o!vY)7Dmdv7kGtxD<*DErL{07ujUtE#J$hvQ@IiPe`6vJ zoH=v8`RAN}g2ry!Y{k0Pv9bNGku^ljde-teif`g+Q7)zjhx;h{7I-0=fwirvOuV|M zH!fBcYl@+Ca1GEYP*<}rJ!(o3#mU+Ac~YeBE_Y>`6pwC^=1F$@oZgj%St!;2o}mbR zvybv3d&zPgMM6L4K6ei_jC^;qZ3J4>c&Y2LT;OBJJ2HRX)VU=#QEW+%Uux!0w0D&G zG5oly@d88NRYp&ZzNf5jdXAnN53oJeX!{DY&(oRyfFEYD=l_+lq m6;t|S!-_GZ){QDg|4X^Y|AvD1=()$oiKg-&?tbL*NU;R^!X#?| literal 0 HcmV?d00001 diff --git a/vlkx-resources/shader/SPIRV/basic.vert.spv b/vlkx-resources/shader/SPIRV/basic.vert.spv new file mode 100644 index 0000000000000000000000000000000000000000..e620da9e46e527cfb157100bc309a773aa9be4c9 GIT binary patch literal 1752 zcmZ9L*-jKu5QdM;G@A+{vZ#nN;Ep1$F)=QH$#6jwC2%`wpa+`_-K3{SjaR&aK9W!6 zjfwwvx@$6eNKMuM*K+DqHI;$IA?MDxQ8(0Wr>oTPupqcbE~-^kcqvu+?J+V0xt<{|8{^HmNA$>Ox@bX<38PHOY+P=@ZO+r}R54U6XK@ zN<1Q)`g6jg(!|5f%VHl^10PaT+&O9P#Q(CyeAjT?GbUY=P?vka;}XuSFuS5Iea)A< zg)`xfi^9B#`Em!C`NCPvOC=_Lss*TW^${RFAt^a=g>xZj~Hw-qOUG`E3dL#N%f-KU5dr9SQdnL(lB$ do`i3bo?mNXVB)N|W!dZpAA1A;(X?Mk{sJ^hbXoua literal 0 HcmV?d00001 diff --git a/vlkx-resources/shader/SPIRV/rt.stage1.frag.spv b/vlkx-resources/shader/SPIRV/rt.stage1.frag.spv new file mode 100644 index 0000000000000000000000000000000000000000..77f6fead66d33071db67f1a8a2773258256f06e1 GIT binary patch literal 28044 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 CYq~f9 literal 0 HcmV?d00001 diff --git a/vlkx-resources/shader/SPIRV/rt.stage1.vert.spv b/vlkx-resources/shader/SPIRV/rt.stage1.vert.spv new file mode 100644 index 0000000000000000000000000000000000000000..098ddba1d8a3c682b8ff37077cebb67a64698ceb GIT binary patch literal 2136 zcmZ9N+fGwa5Qdj6EeMJtM-{ak!~=++fGBdPpn!rPB;1-E+%Y>TyQVFO-biA+HSryM z2=DZ*JR~Om-(pSDxSGk#{4;A#Yj&E=3^Y1-!Zo`lx8aUYo687suEpg&UtZf@8?Dw} zjZRFC;nC`{-VvWO`f(aWdr^`ipA?-EVYwi>B)TFR6b*}tq9svQzl``-1%2#pWH^ZJ2vu$8N- zLG|WcqsN(($CI?SoxG3nGaLQOwpdEuEG5-ilp$0i9W(bI^GgbY_n}81!7w>EHZURE_o7BAjCw@xkDRQ6t!KEm?op z>ZT{Ft6klI_sJ7qR z#i`l)7zj3S;-T|q_*)%AlASyHKBDPx_nL^>jIeW;T*D%}OV1-Bj`e(7vf1hJ4o2sG z^y>bc1TcIqhuyHbV`J_M;`GYikQ2Qq0%OjP#0w(Jfxal>;Lkg;D`qF(6Oqn>^}FKL zqj~H1nRKwBz>%qCU7WnsIHh_s;>?u;{!0jZKPwcU(;{|cL)*stv<3{ELruh((`|MS zdr=YCjdl_H#Lf_px;sSR-{ix7bO(%g`KcJ^#W{cEkF7`4C!#K7tLvgGxcJ9(cLBxJhGkb{sTk*+F9`fPaFMd_TZhcX`%;S2%*eA<>oR4Ga8i)Za)0s;KPoY&$wjpO-+@a?uxhv&VE{N;MU88WO_m8&ZLO` ztb?kFQ3DkEeDKm#_C&@%r{X_AA|o>;oQOJQ^3|FgY#YSF6-jY Ry?p$wQWrXYe^vdo=pS*zgE#;H literal 0 HcmV?d00001 diff --git a/vlkx-resources/shader/SPIRV/rt.stage2.frag.spv b/vlkx-resources/shader/SPIRV/rt.stage2.frag.spv new file mode 100644 index 0000000000000000000000000000000000000000..3d3bc48b4c19ea6ac786c04a2d1dcb2b43088344 GIT binary patch literal 24228 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 literal 0 HcmV?d00001 diff --git a/vlkx-resources/shader/SPIRV/rt.stage2.vert.spv b/vlkx-resources/shader/SPIRV/rt.stage2.vert.spv new file mode 100644 index 0000000000000000000000000000000000000000..01bc53f188c421f98baac4ed35b19e683a881ca1 GIT binary patch literal 1796 zcmYk5*-jKe6o!l0Ku{2oMMdlpcW~SQQDhlsl92?%D8^gU!H(%vMx{z%|{`8g&IR&JDSWm#wXxty(XCTU%JX zi^Z@jdP8hR+)2ML>3=UyGn5m;DPdfg6mVJ)>OxV!g5*y*mt9G}|9)lV!*?@F^EB=> z;=QCBONS?3njIwFUh+K7yXnWkljCLlshM_@-8|j*`H4$jS?3bozb5&v^QN72UyDs} zSJaAwXK}XQQ(lFrxPH&mpXbvo-$_3w*x4C9=&aF6-#61<9%s9W@)Uiq*s-VQ4+rea zzIP?q%w8SkZmC9koe{``PLJrsMyEG)7CJqd{!B5g_91WKoN>g1LmEd+I40N9KfacS zI*->;4?5>#vO-qngD?9D?JAOyFsZuqfV|FmBz-`VJ^V`0=@kz9K}r0X{gnQx3l1HB zUR0j|k3Twh12-of-w}aj`f0Bn{+d@-VvP#e7&y0DxL2!nLVMy`t?1k{+_-X0Nk#&T zT4qKzpV|n*vy0(+I?_F%sZ0Q$Bd{Moo@(^n(j$P z0(r@SeqUhW|3H#6;^L3ajLn|>OM=#h)wt}{!*#pA73px?8xjzERgyZ0%`Dd?iO0L( zd_$6RcygoH1-K>kiyd=j!Tk|amRuFfyoUv5{X;A{&IFEmZtG<+r?J3u#-cCceo+k0 z&kESADFg2gy{>r7>Vm+0nISo;eOiDw8@>f>n336B(jJ~1_(TF+TN*hp3&e?psz7eu z8NBs2t3AD;<8wv8#_GNrxEHFG9M=RmmT}j$hofiXZUl}yXe%=Jc2mF~d(Qa|>`rfK zPc7Kkoz7_w4_L%Mey8)&v9Z3;>4RL00`Kd4nAyX?@y4v4N7~yvGWj*wKh_yO*nA7z v6Yb%7hs=d{w1ri6&>| literal 0 HcmV?d00001 diff --git a/vlkx-resources/shader/SPIRV/rt.stage3.frag.spv b/vlkx-resources/shader/SPIRV/rt.stage3.frag.spv new file mode 100644 index 0000000000000000000000000000000000000000..3d01c6e538bcafda8c7b537ede024eb7871a3261 GIT binary patch literal 23064 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 literal 0 HcmV?d00001 diff --git a/vlkx-resources/shader/SPIRV/rt.stage3.vert.spv b/vlkx-resources/shader/SPIRV/rt.stage3.vert.spv new file mode 100644 index 0000000000000000000000000000000000000000..a93f95d1abc380af46e996515cb84fa336c52760 GIT binary patch literal 2120 zcmYk7+fGwa5QZ0&76e5RIjD$5JcD=wM3GaemMTaU>+I*}JLf znLBv&x}0~!=cN8P9irVRsUsf~oe&L(hD4V|4@C2#is-p$U6j*byZBE9$}2)^jJ$Fa zr&>?4B-&YxcH>4Q9h}%neLrrb@kW$2l1~9A->c|zIcdb(S+eJAAuct@I}`KwsizdB zacwVEaPup;8R74D-qzyA8+pfsRkkLMjLohTT&MS^2OdxA*;eu;#?NfLO}5pY<9yT~iJ8fYZ}tM;P(J;O0jTFz#0L%U#RMyNGLbF?V#c^DcB|PF?H+ zdO_4LFZ{+7li0|ElCTv;L-N%xWEzuJarJaS7z6M(ROl?qDO*r^SgyjM19b%9eWI{OEP{aKK0*68dbcKn$=`$%oG>IZSz6AnIqgyh8+gc74y#I76)<$b3E z#y)InGnsRLz_D?NP28`Ff&GjKpG9S0Z_!s2k6E7=u^;RJIeF`d2;6-5cJN__=5tXp zI61ge6oJ*Gk@J#>I7QKzh}`TcI5F_KA_9A;$)(72C3c4|5d zbG$2=TJW(s-jfUtafnYmtL?sIe5{w~yo2{Y7P0d`Lj1*mvG>-aWy!YN#=iys71_9h z&-Z|>N(L`^p7-B&Su)=y-ye0L<7aiONv4iX&4q8IDguKzyoq}iacnl14as~L*!W)1 J|H^w#^bcJ^e?R~L literal 0 HcmV?d00001 diff --git a/vlkx-resources/shader/basic.frag b/vlkx-resources/shader/basic.frag new file mode 100644 index 0000000..db7f6f5 --- /dev/null +++ b/vlkx-resources/shader/basic.frag @@ -0,0 +1,9 @@ +#version 450 +#extension GL_ARB_separate_shader_objects : enable + +layout (location = 0) in vec3 fragColor; +layout (location = 0) out vec4 outColor; + +void main() { + outColor = vec4(fragColor, 1.0f); +} \ No newline at end of file diff --git a/vlkx-resources/shader/basic.vert b/vlkx-resources/shader/basic.vert new file mode 100644 index 0000000..64500f8 --- /dev/null +++ b/vlkx-resources/shader/basic.vert @@ -0,0 +1,20 @@ +#version 450 +#extension GL_ARB_separate_shader_objects : enable + +layout (binding = 0) uniform UniformBufferObject{ + mat4 model; + mat4 view; + mat4 proj; +} ubo; + +layout (location = 0) in vec3 inPosition; +layout (location = 1) in vec3 inNormal; +layout (location = 2) in vec3 inColor; +layout (location = 3) in vec2 inUV; + +layout (location = 0) out vec3 fragColor; + +void main() { + gl_Position = ubo.proj * ubo.view * ubo.model * vec4(inPosition, 1.0); + fragColor = inColor; +} \ No newline at end of file diff --git a/vlkx-resources/shader/compileShaders.bat b/vlkx-resources/shader/compileShaders.bat new file mode 100644 index 0000000..5a91861 --- /dev/null +++ b/vlkx-resources/shader/compileShaders.bat @@ -0,0 +1,5 @@ +@echo off +echo Compiling GLSL shaders to SPIR-V Binary +for /r %%i in (*.vert;*.frag) do %VULKAN_SDK%\Bin\glslangValidator.exe -V "%%i" -o "%%~dpiSPIRV\%%~nxi".spv + +PAUSE \ No newline at end of file diff --git a/vlkx-resources/x64/VFFEdit/vlkx-resources.exe.recipe b/vlkx-resources/x64/VFFEdit/vlkx-resources.exe.recipe new file mode 100644 index 0000000..76f3794 --- /dev/null +++ b/vlkx-resources/x64/VFFEdit/vlkx-resources.exe.recipe @@ -0,0 +1,11 @@ + + + + + C:\Users\nurgi\Documents\vlkx\x64\VFFEdit\vlkx-resources.exe + + + + + + \ No newline at end of file diff --git a/vlkx-resources/x64/VFFEdit/vlkx-resources.log b/vlkx-resources/x64/VFFEdit/vlkx-resources.log new file mode 100644 index 0000000..6f650eb --- /dev/null +++ b/vlkx-resources/x64/VFFEdit/vlkx-resources.log @@ -0,0 +1,7 @@ + "{0} -> {1} (Partial -> Full PDB)" + System.FormatException: Index (zero based) must be greater than or equal to zero and less than the size of the argument list. + at System.Text.StringBuilder.AppendFormatHelper(IFormatProvider provider, String format, ParamsArray args) + at System.String.FormatHelper(IFormatProvider provider, String format, ParamsArray args) + at System.String.Format(IFormatProvider provider, String format, Object[] args) + at Microsoft.Build.Framework.LazyFormattedBuildEventArgs.FormatString(CultureInfo culture, String unformatted, Object[] args) +EXEC : fatal error CMF1106: failed to open input PDB file for reading (PDB error code = 4) diff --git a/vlkx-resources/x64/VFFEdit/vlkx-resources.tlog/vlkx-resources.lastbuildstate b/vlkx-resources/x64/VFFEdit/vlkx-resources.tlog/vlkx-resources.lastbuildstate new file mode 100644 index 0000000..6a18dda --- /dev/null +++ b/vlkx-resources/x64/VFFEdit/vlkx-resources.tlog/vlkx-resources.lastbuildstate @@ -0,0 +1,2 @@ +PlatformToolSet=v143:VCToolArchitecture=Native64Bit:VCToolsVersion=14.30.30705:TargetPlatformVersion=10.0.19041.0: +VFFEdit|x64|C:\Users\nurgi\Documents\vlkx\|

kdU}#`u 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 z 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Μs91!zb~_A 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%0DW^Dm4^$e_viI!x4iPt={%~j1iGa!-@gC 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