diff --git a/.clang-tidy b/.clang-tidy index e4ae3e9..9b6c5d9 100644 --- a/.clang-tidy +++ b/.clang-tidy @@ -1,7 +1,7 @@ Checks: '-*,readability-identifier-naming' CheckOptions: - { key: readability-identifier-naming.NamespaceCase, value: camelBack } - - { key: readability-identifier-naming.NamespaceIgnoredRegexp, value: _* } + - { key: readability-identifier-naming.NamespaceIgnoredRegexp, value: _.* } - { key: readability-identifier-naming.EnumCase, value: CamelCase } - { key: readability-identifier-naming.ClassCase, value: CamelCase } @@ -16,6 +16,7 @@ CheckOptions: - { key: readability-identifier-naming.ParameterCase, value: camelBack } - { key: readability-identifier-naming.ClassMemberCase, value: camelBack } - { key: readability-identifier-naming.EnumConstantCase, value: camelBack } + - { key: readability-identifier-naming.ClassMemberPrefix, value: s_ } - { key: readability-identifier-naming.PrivateMethodCase, value: camelBack } - { key: readability-identifier-naming.ProtectedMethodCase, value: camelBack } diff --git a/archimedes_bin/examples/NvrhiRendererTestApp.h b/archimedes_bin/examples/NvrhiRendererTestApp.h new file mode 100644 index 0000000..9295b3c --- /dev/null +++ b/archimedes_bin/examples/NvrhiRendererTestApp.h @@ -0,0 +1,89 @@ +#pragma once + +#include +#include +#include + +struct VelocityComponent { + arch::math::float3 velocity; +}; + +class NvrhiRendererTestApp: public arch::Application { + void init() override { + arch::Ref testScene = arch::createRef(); + + { + arch::ecs::Entity e = testScene->newEntity(); + testScene->domain().addComponent( + e, + { + { 0.0f, 0.0f, 0.0f }, + { 0.0f, 0.0f, 0.0f, 1.0f }, + { 1.0f, .5f, 0.0f }, + } + ); + + // 2D square + // struct Vertex { + // float3 position; + // float3 color; + // float2 tex_coords; + // }; + + // std::vector vertices{ + // { float3(0.5f, 0.5f, 0.0f), {}, float2(1.0f, 1.0f) }, + // { float3(0.5f, -0.5f, 0.0f), {}, float2(1.0f, 0.0f) }, + // { float3(-0.5f, -0.5f, 0.0f), {}, float2(0.0f, 0.0f) }, + // { float3(-0.5f, 0.5f, 0.0f), {}, float2(0.0f, 1.0f) } + // }; + // std::vector indices{ 0, 1, 3, 1, 2, 3 }; + + // Ref vShader = Shader::load("shaders/vertex_shader.sprv"); + // Ref fShader = Shader::load("shaders/fragment_shader.sprv"); + // Ref material = Material::create(vShader, fShader); + // material->setTexture("_mainTxt", TextureLoader::read_file("textures/.jpg")); + // material->SetFloat("_mixValue", 0.2f); + // material->SetFloat3("_pos", glm::vec3(0.5f, 0.5f, 0.5f)); + // material->SetColor("_color", glm::vec3(1.0f, 0.0f, 0.0f)); + + // Ref mesh = Mesh::create(vertices, indices); + testScene->domain().addComponent(e, { /*mesh*/ }); + testScene->domain().addComponent(e, arch::float3{ 0.0f, .01f, 0.0f }); + } + + { + arch::ecs::Entity e = testScene->newEntity(); + testScene->domain().addComponent( + e, + { + { 0.5f, 0.5f, 0.0f }, + { 0.0f, 0.0f, 0.0f, 1.0f }, + arch::float3(1) + } + ); + testScene->domain().addComponent(e, { /*mesh*/ }); + testScene->domain().addComponent(e, arch::float3{ 0.0f, -.01f, 0.001f }); + } + + arch::scene::SceneManager::get()->changeScene(testScene); + } + + void update() override { + auto view = arch::scene::SceneManager::get() + ->currentScene() + ->domain() + .view(); + + for (auto [entity, transform, velocity] : view.all()) { + if (transform.position.y < -.5f || transform.position.y > .5f) { + velocity.velocity *= -1; + } + if (transform.position.x < -.5f || transform.position.x > .5f) { + velocity.velocity.x *= -1; + } + + transform.position.x += velocity.velocity.x; + transform.position.y += velocity.velocity.y; + } + } +}; diff --git a/archimedes_bin/main.cpp b/archimedes_bin/main.cpp index 507d013..c0ee83a 100644 --- a/archimedes_bin/main.cpp +++ b/archimedes_bin/main.cpp @@ -1,16 +1,17 @@ +#include "examples/NvrhiRendererTestApp.h" #include #include -struct MyApp: arch::Application { - void init() override { arch::Logger::info("Initializing user app!"); } -}; - int main() { arch::Logger::init(arch::LogLevel::trace); - arch::EngineConfig config{ 600, 480, "Archimedes Test", glm::vec4(0, 0, 0, 0) }; + arch::Ref myApp = arch::createRef(); - arch::Ref myApp = arch::createRef(); + arch::EngineConfig config{ .windowWidth = 600, + .windowHeight = 480, + .windowTitle = "Archimedes Test", + .backgroundColor = arch::Color(.03f, .03f, .03, 1.f), + .renderingApi = arch::gfx::RenderingAPI::Nvrhi_VK }; arch::Engine engine{ config, myApp }; engine.start(); diff --git a/cmake/library.cmake b/cmake/library.cmake index 6e3de37..ea81fc8 100644 --- a/cmake/library.cmake +++ b/cmake/library.cmake @@ -5,9 +5,21 @@ include("${PROJECT_SOURCE_DIR}/cmake/conan.cmake") add_library(${PROJECT_NAME}) # find source files -file(GLOB_RECURSE ARCHIMEDES_SOURCE src/**.cpp) +file(GLOB_RECURSE ARCHIMEDES_SOURCE CONFIGURE_DEPENDS src/**.cpp) target_sources(${PROJECT_NAME} PRIVATE ${ARCHIMEDES_SOURCE}) target_include_directories(${PROJECT_NAME} PUBLIC include) # link conan libraries target_link_libraries(${PROJECT_NAME} PUBLIC ${ARCHIMEDES_LIBRARIES}) + + +include(FetchContent) +FetchContent_Declare( + nvrhi + GIT_REPOSITORY https://github.com/NVIDIAGameWorks/NvRhi.git + GIT_TAG main + OVERRIDE_FIND_PACKAGE +) +find_package(nvrhi REQUIRED) + +target_link_libraries(${PROJECT_NAME} PRIVATE nvrhi_vk nvrhi) diff --git a/conanfile.py b/conanfile.py index f995d36..4e1452f 100644 --- a/conanfile.py +++ b/conanfile.py @@ -21,7 +21,7 @@ def requirements(self): self.requires("vulkan-memory-allocator/cci.20231120") # SPIRV (Shader compiler) - # self.requires("shaderc/2021.1") # waiting for conan repo update + self.requires("shaderc/2023.6") # waiting for conan repo update def configure(self): if is_msvc(self, True): diff --git a/include/Engine.h b/include/Engine.h index d45cdd9..9bed858 100644 --- a/include/Engine.h +++ b/include/Engine.h @@ -1,6 +1,5 @@ #pragma once -#include #include #include "Application.h" @@ -10,12 +9,16 @@ #include namespace arch { +namespace scene { +class SceneManager; +} struct EngineConfig { - int window_width; - int window_height; - std::string window_title; - glm::vec4 background_color; + int windowWidth; + int windowHeight; + std::string windowTitle; + Color backgroundColor; + gfx::RenderingAPI renderingApi; }; /** @@ -32,13 +35,14 @@ class Engine { void start(); private: - Window _window; + Ref _mainWindow; EngineConfig _engineConfig; - gfx::Renderer _renderer; + Ref _renderer; Ref _application; - FRIEND_TEST(EngineTest, ConfigWindowTest); + Ref _sceneManager; +private: /** * Responsible for drawing game on the screen. */ @@ -52,7 +56,10 @@ class Engine { /** * Clearing all previously allocated GLFW and Engine resources. */ - static void _terminate(); + void _shutdown(); + +private: + FRIEND_TEST(EngineTest, ConfigWindowTest); }; } // namespace arch diff --git a/include/Gfx.h b/include/Gfx.h new file mode 100644 index 0000000..1c27223 --- /dev/null +++ b/include/Gfx.h @@ -0,0 +1,5 @@ +#pragma once + +#include "gfx/Buffer.h" +#include "gfx/Mesh.h" +#include "gfx/Renderer.h" diff --git a/include/Logger.h b/include/Logger.h index df5359d..339ded2 100644 --- a/include/Logger.h +++ b/include/Logger.h @@ -25,7 +25,7 @@ enum class LogLevel { }; /// @brief Implementation details. -namespace _details { // NOLINT(*-identifier-naming) +namespace _details { template struct UniversalLogger; @@ -82,8 +82,12 @@ class Logger { private: template - static void - _log_impl(LogLevel level, std::source_location loc, spdlog::format_string_t fmt, Args&&... args); + static void _logImpl( + LogLevel level, + std::source_location loc, + spdlog::format_string_t fmt, + Args&&... args + ); static std::shared_ptr s_logger; }; diff --git a/include/Logger.hpp b/include/Logger.hpp index 0cf896d..8f50169 100644 --- a/include/Logger.hpp +++ b/include/Logger.hpp @@ -7,7 +7,7 @@ namespace arch { template -void Logger::_log_impl( +void Logger::_logImpl( LogLevel level, const std::source_location loc, spdlog::format_string_t fmt, @@ -41,7 +41,7 @@ struct UniversalLogger { std::source_location loc = std::source_location::current() ) { // Pass all arguments to the logger - Logger::_log_impl(level, loc, fmt, std::forward(args)...); + Logger::_logImpl(level, loc, fmt, std::forward(args)...); } }; diff --git a/include/Mmath.h b/include/Mmath.h new file mode 100644 index 0000000..b978b2e --- /dev/null +++ b/include/Mmath.h @@ -0,0 +1,9 @@ +#pragma once + +#include "math/Math.h" + +namespace arch { + +using namespace arch::math; + +} // namespace arch diff --git a/include/Ref.h b/include/Ref.h index a82ac3d..4427347 100644 --- a/include/Ref.h +++ b/include/Ref.h @@ -7,9 +7,19 @@ namespace arch { template using Ref = std::shared_ptr; +template +using WeakRef = std::weak_ptr; + template +requires std::constructible_from constexpr Ref createRef(Args&&... args) { return std::make_shared(std::forward(args)...); } +template +requires std::derived_from> +constexpr Ref createRef(T* ptr) { + return ptr->shared_from_this(); +} + } // namespace arch diff --git a/include/Scene.h b/include/Scene.h index 704996d..37b3d07 100644 --- a/include/Scene.h +++ b/include/Scene.h @@ -1,42 +1,4 @@ #pragma once - -#include -#include - -namespace arch { - -class Scene { -public: - - /// @brief Hierarchy node class - using Node = hier::HierarchyNode; - - /// @brief Default constructor - Scene() noexcept; - - /// @brief Creates new entity - /// @see arch::ecs::Domain::newEntity() - ecs::Entity newEntity() noexcept; - /// @brief Kills entity - /// @see arch::ecs::Domain::kill(const ecs::Entity) - void removeEntity(const ecs::Entity entity) noexcept; - - /// @brief Returns ecs::Domain of this scene - ecs::Domain& domain() noexcept; - /// @brief Returns readonly ecs::Domain of this scene - const ecs::Domain& domain() const noexcept; - - /// @brief Returns root entity - ecs::Entity root() const noexcept; - /// @brief Returns root node - Node& rootNode() noexcept; - /// @brief Returns readonly root node - const Node& rootNode() const noexcept; - -private: - - ecs::Domain _domain; - Node* _rootNode; -}; - -} // namespace arch +#include "scene/Components.h" +#include "scene/Scene.h" +#include "scene/SceneManager.h" diff --git a/include/Window.h b/include/Window.h index 317306f..75fb95b 100644 --- a/include/Window.h +++ b/include/Window.h @@ -2,7 +2,6 @@ #include -#include #include namespace arch { @@ -33,8 +32,6 @@ class Window { */ GLFWwindow* get() const; - void clear(Color color) const; - void clear(float r, float g, float b, float a) const; void swapBuffers() const; void resize(int width, int height) const; void setTitle(const std::string& title) const; @@ -54,9 +51,7 @@ class Window { * @param monitor The monitor to use for fullscreen mode. * @param window The window whose context to share resources with, or NULL to not share resources. */ - void initialize(int width, int height, const char* name, GLFWmonitor* monitor, GLFWwindow* window); - - static void framebuffer_size_callback(GLFWwindow* window, int width, int height); + void _initialize(int width, int height, const char* name, GLFWmonitor* monitor, GLFWwindow* window); }; } // namespace arch diff --git a/include/gfx/Buffer.h b/include/gfx/Buffer.h new file mode 100644 index 0000000..b119b6b --- /dev/null +++ b/include/gfx/Buffer.h @@ -0,0 +1,13 @@ +#pragma once + +#include "buffer/Buffer.h" +#include "buffer/BufferManager.h" +#include "buffer/BufferType.h" +#include "buffer/IndexBuffer.h" +#include "buffer/VertexBuffer.h" + +namespace arch::gfx { + +using namespace arch::gfx::buffer; + +} // namespace arch::gfx diff --git a/include/gfx/GraphicsFormat.h b/include/gfx/GraphicsFormat.h new file mode 100644 index 0000000..a6d83c4 --- /dev/null +++ b/include/gfx/GraphicsFormat.h @@ -0,0 +1,75 @@ +#pragma once +#include "ArchMath.h" + +namespace arch::gfx { + +enum class GraphicsFormat : u8 { + none, + + r8, + r8s, + r8_norm, + r8s_norm, + + r16, + r16s, + r16_norm, + r16s_norm, + r16f, + + rg8, + rg8s, + rg8_norm, + rg8s_norm, + + bgra4_norm, + b5g6r5_norm, + b5g5r5a1_norm, + rgba8, + rgba8s, + rgba8_norm, + rgba8s_norm, + bgra8_norm, + srgba8_norm, + sbgra8_norm, + + r10g10b10a2_norm, + r11g11b10f, + + rg16, + rg16s, + rg16_norm, + rg16s_norm, + rg16f, + + r32, + r32s, + r32f, + + rgba16, + rgba16s, + rgba16f, + rgba16_norm, + rgba16s_norm, + + rg32, + rg32s, + rg32f, + + rgb32, + rgb32s, + rgb32f, + + rgba32, + rgba32s, + rgba32f, + + depth16, + depth24Stencil8, + depth32f, + depth32fStencil8, + + _COUNT, +}; + +} // namespace arch::gfx diff --git a/include/gfx/Mesh.h b/include/gfx/Mesh.h new file mode 100644 index 0000000..4fb42b6 --- /dev/null +++ b/include/gfx/Mesh.h @@ -0,0 +1,37 @@ +#pragma once + +#include +#include + +#include "Mmath.h" +#include "Ref.h" +#include "buffer/IndexBuffer.h" +#include "buffer/VertexBuffer.h" + +namespace arch::gfx { + +class Mesh final { +public: + Mesh(const Ref& vertices, const Ref& indices); + ~Mesh(); + +public: + template + static Ref create(std::span vertices, std::span indices); + static Ref create(Ref vertices, Ref indices); + +public: + Mesh(const Mesh&) = delete; + Mesh& operator=(const Mesh&) = delete; + + Mesh(Mesh&& other) noexcept = default; + Mesh& operator=(Mesh&& other) noexcept = default; + +private: + Ref _vertexBuffer; + Ref _indexBuffer; +}; + +} // namespace arch::gfx + +#include "Mesh.hpp" diff --git a/include/gfx/Mesh.hpp b/include/gfx/Mesh.hpp new file mode 100644 index 0000000..af1d652 --- /dev/null +++ b/include/gfx/Mesh.hpp @@ -0,0 +1,18 @@ +#pragma once + +#include "Mesh.h" +#include "Renderer.h" +#include "buffer/BufferManager.h" + +namespace arch::gfx { + +template +Ref Mesh::create(std::span vertices, std::span indices) { + Ref bufferManager = Renderer::getCurrent()->getBufferManager(); + + Ref vertexBuffer = bufferManager->createVertexBuffer(vertices); + Ref indexBuffer = bufferManager->createIndexBuffer(indices); + return create(vertexBuffer, indexBuffer); +} + +} // namespace arch::gfx diff --git a/include/gfx/Renderer.h b/include/gfx/Renderer.h index 11970f0..691b3f5 100644 --- a/include/gfx/Renderer.h +++ b/include/gfx/Renderer.h @@ -1,17 +1,60 @@ #pragma once -/// @brief Temporary code - -#include +#include "Ref.h" +#include "RenderingAPI.h" +#include "Window.h" +#include "buffer/BufferManager.h" +#include "texture/TextureManager.h" namespace arch::gfx { -class Renderer { +class Mesh; + +class Renderer: public std::enable_shared_from_this { +public: + static Ref create(RenderingAPI api); + + static Ref current() { return s_current; } + +protected: + explicit Renderer(RenderingAPI api, bool debug = true): _api(api), _debug(debug) {} + +public: + virtual ~Renderer() = default; + + static Ref getCurrent(); + + void makeCurrent(); + + virtual void init(const Ref& window) = 0; + virtual void shutdown() = 0; + public: - void init(); + void setClearColor(Color color); + +public: + virtual void onResize(u32 width, u32 height) = 0; + + virtual void beginFrame() = 0; + virtual void present() = 0; + +public: + + virtual void render(const Ref& mesh, const Mat4x4& transform) = 0; + +public: + virtual Ref getBufferManager() = 0; + virtual Ref getTextureManager() = 0; + +protected: + Ref _window = nullptr; + RenderingAPI _api = RenderingAPI::none; + bool _debug = false; + + Color _clearColor = { 0, 0, 0, 0 }; private: - VkInstance _instance{}; + static Ref s_current; }; } // namespace arch::gfx diff --git a/include/gfx/RenderingAPI.h b/include/gfx/RenderingAPI.h new file mode 100644 index 0000000..9d86471 --- /dev/null +++ b/include/gfx/RenderingAPI.h @@ -0,0 +1,15 @@ +#pragma once + +namespace arch::gfx { + +enum class RenderingAPI { + none = 0, + vulkan, + Nvrhi_VK, + Nvrhi_DX11, + Nvrhi_DX12, + // directX, + // metal, +}; + +} // namespace arch::gfx diff --git a/include/gfx/Texture.h b/include/gfx/Texture.h new file mode 100644 index 0000000..5cf4fa8 --- /dev/null +++ b/include/gfx/Texture.h @@ -0,0 +1,12 @@ +#pragma once + +#include "gfx/texture/Texture.h" +#include "gfx/texture/TextureFilterMode.h" +#include "gfx/texture/TextureWrapMode.h" +#include "texture/TextureManager.h" + +namespace arch::gfx { + +using namespace arch::gfx::texture; + +} // namespace arch::gfx diff --git a/include/gfx/buffer/Buffer.h b/include/gfx/buffer/Buffer.h new file mode 100644 index 0000000..8cf9a3f --- /dev/null +++ b/include/gfx/buffer/Buffer.h @@ -0,0 +1,27 @@ +#pragma once + +#include "BufferType.h" +#include "Mmath.h" +#include "Ref.h" + +namespace arch::gfx::buffer { + +class Buffer { +protected: + explicit Buffer(BufferType type): _type(type) {} + + virtual ~Buffer() = default; + +public: + static Ref create(BufferType type); + static Ref create(size_t size, BufferType type); + static Ref create(const void* data, size_t size, BufferType type); + +public: + virtual void setData(void* data, u64 size) const = 0; + +protected: + BufferType _type; +}; + +} // namespace arch::gfx::buffer diff --git a/include/gfx/buffer/BufferManager.h b/include/gfx/buffer/BufferManager.h new file mode 100644 index 0000000..d399533 --- /dev/null +++ b/include/gfx/buffer/BufferManager.h @@ -0,0 +1,35 @@ +#pragma once + +#include + +#include "Buffer.h" +#include "IndexBuffer.h" +#include "VertexBuffer.h" + +namespace arch::gfx::buffer { + +class BufferManager { +public: + BufferManager() = default; + virtual ~BufferManager() = default; + +public: + Ref createVertexBuffer(); + template + Ref createVertexBuffer(std::span vertices); + Ref createVertexBuffer(void* data, u32 size); + + Ref createIndexBuffer(); + Ref createIndexBuffer(std::span indices); + + Ref createBuffer(); + +protected: + virtual Ref _createVertexBufferImpl(void* data, u32 size) = 0; + virtual Ref _createIndexBufferImpl(std::span indices) = 0; + virtual Ref _createBufferImpl(void* data, u32 size) = 0; +}; + +} // namespace arch::gfx::buffer + +#include "BufferManager.hpp" diff --git a/include/gfx/buffer/BufferManager.hpp b/include/gfx/buffer/BufferManager.hpp new file mode 100644 index 0000000..a4f4977 --- /dev/null +++ b/include/gfx/buffer/BufferManager.hpp @@ -0,0 +1,11 @@ +#pragma once +#include "BufferManager.h" + +namespace arch::gfx::buffer { + +template +Ref BufferManager::createVertexBuffer(std::span vertices) { + return _createVertexBufferImpl(vertices.data(), vertices.size_bytes()); +} + +} // namespace arch::gfx::buffer diff --git a/include/gfx/buffer/BufferType.h b/include/gfx/buffer/BufferType.h new file mode 100644 index 0000000..27d8a77 --- /dev/null +++ b/include/gfx/buffer/BufferType.h @@ -0,0 +1,13 @@ +#pragma once + +namespace arch::gfx::buffer { + +enum class BufferType { + vertex, + index, + uniform, + staging, + blob, +}; + +} // namespace arch::gfx::buffer diff --git a/include/gfx/buffer/IndexBuffer.h b/include/gfx/buffer/IndexBuffer.h new file mode 100644 index 0000000..c0f847e --- /dev/null +++ b/include/gfx/buffer/IndexBuffer.h @@ -0,0 +1,12 @@ +#pragma once + +#include "Buffer.h" + +namespace arch::gfx::buffer { + +class IndexBuffer: public virtual Buffer { +public: + IndexBuffer(): Buffer(BufferType::index) {} +}; + +} // namespace arch::gfx::buffer diff --git a/include/gfx/buffer/VertexBuffer.h b/include/gfx/buffer/VertexBuffer.h new file mode 100644 index 0000000..45da965 --- /dev/null +++ b/include/gfx/buffer/VertexBuffer.h @@ -0,0 +1,13 @@ +#pragma once +#include "Buffer.h" + +namespace arch::gfx::buffer { + +class VertexBuffer: public virtual Buffer { +public: + VertexBuffer(): Buffer(BufferType::vertex) {} + + ~VertexBuffer() override = default; +}; + +} // namespace arch::gfx::buffer diff --git a/include/gfx/texture/Texture.h b/include/gfx/texture/Texture.h new file mode 100644 index 0000000..23ae5af --- /dev/null +++ b/include/gfx/texture/Texture.h @@ -0,0 +1,51 @@ +#pragma once + +#include + +#include "Mmath.h" +#include "TextureFilterMode.h" +#include "TextureWrapMode.h" +#include "gfx/GraphicsFormat.h" + +namespace arch::gfx::texture { + +class Texture { +protected: + Texture(GraphicsFormat format, TextureWrapMode wrapMode, TextureFilterMode filterMode, bool isReadable = false); + virtual ~Texture() = default; + +public: + TextureFilterMode getFilter() const; + virtual void setFilter(TextureFilterMode filterMode); + + TextureWrapMode getWrap() const; + virtual void setWrap(TextureWrapMode wrapMode); + + GraphicsFormat getFormat() const; + + bool isReadable() const; + + u32 getWidth() const; + u32 getHeight() const; + u32 getDepth() const; + u32 getSize(u32 axis) const; + +public: + virtual uint3 getSize() const = 0; + + // virtual void setPixels(std::span pixels) = 0; + // virtual void setPixels(Color* pixels, u32 width, u32 height) = 0; + // + // virtual void setPixel(u32 x, u32 y, Color color) = 0; + // + // virtual void readPixels() = 0; + +private: + bool _isReadable; + + GraphicsFormat _format; + TextureWrapMode _wrapMode; + TextureFilterMode _filterMode; +}; + +} // namespace arch::gfx::texture diff --git a/include/gfx/texture/TextureFilterMode.h b/include/gfx/texture/TextureFilterMode.h new file mode 100644 index 0000000..404e744 --- /dev/null +++ b/include/gfx/texture/TextureFilterMode.h @@ -0,0 +1,14 @@ +#pragma once + +namespace arch::gfx::texture { + +enum class TextureFilterMode { + nearest, + linear, + nearestMipmapNearest, + linearMipmapNearest, + nearestMipmapLinear, + linearMipmapLinear, +}; + +} // namespace arch::gfx::texture \ No newline at end of file diff --git a/include/gfx/texture/TextureManager.h b/include/gfx/texture/TextureManager.h new file mode 100644 index 0000000..c48a899 --- /dev/null +++ b/include/gfx/texture/TextureManager.h @@ -0,0 +1,48 @@ +#pragma once + +#include + +#include "Ref.h" +#include "Texture.h" + +namespace arch::gfx::texture { + +class TextureManager { +protected: + TextureManager() = default; + +public: + virtual ~TextureManager() = default; + + Ref createTexture2D( + u32 width, + u32 height, + Color* pixels = nullptr, + GraphicsFormat format = GraphicsFormat::rgba32f, + TextureWrapMode wrapMode = TextureWrapMode::repeat, + TextureFilterMode filterMode = TextureFilterMode::linear, + bool isReadable = false + ); + Ref createTexture2D( + u32 width, + u32 height, + GraphicsFormat format, + void* pixels, + TextureWrapMode wrapMode = TextureWrapMode::repeat, + TextureFilterMode filterMode = TextureFilterMode::linear, + bool isReadable = false + ); + +protected: + virtual Ref _createTexture2DImpl( + u32 width, + u32 height, + GraphicsFormat format, + void* data, + TextureWrapMode wrapMode, + TextureFilterMode filterMode, + bool isReadable + ) = 0; +}; + +} // namespace arch::gfx::texture diff --git a/include/gfx/texture/TextureType.h b/include/gfx/texture/TextureType.h new file mode 100644 index 0000000..70585a3 --- /dev/null +++ b/include/gfx/texture/TextureType.h @@ -0,0 +1,15 @@ +#pragma once + +namespace arch::gfx::texture { + +enum class TextureType { + texture2D, + // texture2DArray, + + // texture3D, + // texture3DArray, + + // textureCube, +}; + +} // namespace arch::gfx::texture diff --git a/include/gfx/texture/TextureWrapMode.h b/include/gfx/texture/TextureWrapMode.h new file mode 100644 index 0000000..d852d3a --- /dev/null +++ b/include/gfx/texture/TextureWrapMode.h @@ -0,0 +1,12 @@ +#pragma once + +namespace arch::gfx::texture { + +enum class TextureWrapMode { + repeat, + mirroredRepeat, + clampToEdge, + clampToBorder, +}; + +} // namespace arch::gfx::texture diff --git a/include/hier/HierarchyNode.h b/include/hier/HierarchyNode.h index 0b95011..dad3b6b 100644 --- a/include/hier/HierarchyNode.h +++ b/include/hier/HierarchyNode.h @@ -8,10 +8,8 @@ #include #include -namespace arch { - +namespace arch::scene { class Scene; - } namespace arch::hier { @@ -94,7 +92,7 @@ class HierarchyNode: public ChildNode { private: - friend class ::arch::Scene; + friend class scene::Scene; friend class _details::ChildrenIterator; friend class _details::HierarchyIterator; diff --git a/include/math/Math.h b/include/math/Math.h index 0876239..31b62d2 100644 --- a/include/math/Math.h +++ b/include/math/Math.h @@ -1,5 +1,7 @@ #pragma once +#include +#include #include #include #include @@ -11,6 +13,8 @@ namespace arch::math { using Byte = std::byte; +// NOLINTBEGIN(*-identifier-naming) + using i8 = int8_t; using i16 = int16_t; using i32 = int32_t; @@ -37,10 +41,14 @@ using uint2 = glm::uvec2; using uint3 = glm::uvec3; using uint4 = glm::uvec4; +// NOLINTEND(*-identifier-naming) + using Mat2x2 = glm::mat2; using Mat3x3 = glm::mat3; using Mat4x4 = glm::mat4; using Color = glm::vec4; +using Quat = glm::qua; + } // namespace arch::math diff --git a/include/scene/Components.h b/include/scene/Components.h new file mode 100644 index 0000000..d98abd6 --- /dev/null +++ b/include/scene/Components.h @@ -0,0 +1,4 @@ +#pragma once + +#include "components/MeshComponent.h" +#include "components/TransformComponent.h" diff --git a/include/scene/Scene.h b/include/scene/Scene.h new file mode 100644 index 0000000..0585f39 --- /dev/null +++ b/include/scene/Scene.h @@ -0,0 +1,42 @@ +#pragma once + +#include +#include + +namespace arch::scene { + +class Scene { +public: + + /// @brief Hierarchy node class + using Node = hier::HierarchyNode; + + /// @brief Default constructor + Scene() noexcept; + + /// @brief Creates new entity + /// @see arch::ecs::Domain::newEntity() + ecs::Entity newEntity() noexcept; + /// @brief Kills entity + /// @see arch::ecs::Domain::kill(const ecs::Entity) + void removeEntity(const ecs::Entity entity) noexcept; + + /// @brief Returns ecs::Domain of this scene + ecs::Domain& domain() noexcept; + /// @brief Returns readonly ecs::Domain of this scene + const ecs::Domain& domain() const noexcept; + + /// @brief Returns root entity + ecs::Entity root() const noexcept; + /// @brief Returns root node + Node& rootNode() noexcept; + /// @brief Returns readonly root node + const Node& rootNode() const noexcept; + +private: + + ecs::Domain _domain; + Node* _rootNode; +}; + +} // namespace arch::scene diff --git a/include/scene/SceneManager.h b/include/scene/SceneManager.h new file mode 100644 index 0000000..956c852 --- /dev/null +++ b/include/scene/SceneManager.h @@ -0,0 +1,29 @@ +#pragma once + +#include "Ref.h" +#include "Scene.h" + +namespace arch::gfx { +class Renderer; +} + +namespace arch::scene { + +class SceneManager { +public: + SceneManager(); + + void update(); + void renderScene(const Ref& renderer); + + const Ref& currentScene() const noexcept; + void changeScene(const Ref& scene); + +private: + Ref _currentScene; + +public: + static Ref& get(); +}; + +} // namespace arch::scene diff --git a/include/scene/components/MeshComponent.h b/include/scene/components/MeshComponent.h new file mode 100644 index 0000000..0c3a1d3 --- /dev/null +++ b/include/scene/components/MeshComponent.h @@ -0,0 +1,11 @@ +#pragma once + +#include "gfx/Mesh.h" + +namespace arch::scene::components { + +struct MeshComponent { + Ref mesh; +}; + +} // namespace arch::scene::components diff --git a/include/scene/components/TransformComponent.h b/include/scene/components/TransformComponent.h new file mode 100644 index 0000000..14baabf --- /dev/null +++ b/include/scene/components/TransformComponent.h @@ -0,0 +1,16 @@ +#pragma once +#include "ArchMath.h" + +namespace arch::scene::components { + +struct TransformComponent { + float3 position; + Quat rotation; + float3 scale; + + Mat4x4 getTransformMatrix() const noexcept { + return glm::translate(Mat4x4(1.f), position) * glm::toMat4(rotation) * glm::scale(Mat4x4(1.f), scale); + } +}; + +} // namespace arch::scene::components diff --git a/src/Engine.cpp b/src/Engine.cpp index 6591a64..2bd3eb0 100644 --- a/src/Engine.cpp +++ b/src/Engine.cpp @@ -1,19 +1,21 @@ #include "Engine.h" -#include "ArchMath.h" +#include "Exception.h" +#include "Gfx.h" #include "InputHandler.h" #include "Logger.h" -#include "exceptions/GLFWException.h" #include "resource/ModelLoader.h" #include "resource/TextureLoader.h" +#include "scene/SceneManager.h" namespace arch { Engine::Engine(const EngineConfig& config, const Ref& application): - _window{1, 1, {}}, _engineConfig{config}, _application{application} {} + _engineConfig{ config }, + _application{ application } {} Engine::~Engine() { - _terminate(); + _shutdown(); } void Engine::start() { @@ -33,81 +35,55 @@ void Engine::start() { void Engine::_mainLoop() { Logger::info("Starting engine main loop"); - // 3D cube - // std::vector vertices { - // { glm::vec3(0.5f, 0.5f, 0.5f), {}, {}}, - // { glm::vec3(-0.5f, 0.5f, 0.5f), {}, {}}, - // { glm::vec3(-0.5f, -0.5f, 0.5f), {}, {}}, - // { glm::vec3(0.5f, -0.5f, 0.5f), {}, {}}, - // { glm::vec3(0.5f, 0.5f, -0.5f), {}, {}}, - // { glm::vec3(-0.5f, 0.5f, -0.5f), {}, {}}, - // { glm::vec3(-0.5f, -0.5f, -0.5f), {}, {}}, - // { glm::vec3(0.5f, -0.5f, -0.5f), {}, {}} - // }; - // std::vector indices { - // 0, 1, 2, 0, 3, 2, - // 4, 5, 6, 4, 7, 6, - // 4, 0, 3, 4, 7, 3, - // 5, 1, 2, 5, 6, 2, - // 7, 6, 2, 7, 3, 2, - // 4, 5, 1, 5, 0, 1 - // }; - // 2D square - struct Vertex { - float3 position; - float3 color; - float2 tex_coords; - }; - - std::vector vertices{ - { float3(0.5f, 0.5f, 0.0f), {}, float2(1.0f, 1.0f) }, - { float3(0.5f, -0.5f, 0.0f), {}, float2(1.0f, 0.0f) }, - { float3(-0.5f, -0.5f, 0.0f), {}, float2(0.0f, 0.0f) }, - { float3(-0.5f, 0.5f, 0.0f), {}, float2(0.0f, 1.0f) } - }; - std::vector indices{ 0, 1, 3, 1, 2, 3 }; - // Model model { { { vertices, indices } } }; - // TextureLoader texture_loader; - // Renderer3D renderer {}; - // renderer.set_texture(texture_loader.read_file("pawelskrzynski.jpg")); - // renderer.submit(model); - - InputHandler::get().initialize(_window.get()); - - while (!_window.shouldClose()) { - _window.clear(_engineConfig.background_color); - - // renderer.render(); + InputHandler::get().initialize(_mainWindow->get()); + + while (!_mainWindow->shouldClose()) { + // Update the application _application->update(); - _window.swapBuffers(); + _sceneManager->update(); + + // Render the scene + _renderer->beginFrame(); + + _sceneManager->renderScene(_renderer); + + _renderer->present(); + + _mainWindow->swapBuffers(); glfwPollEvents(); } } void Engine::_initialize() { - if (!glfwInit()) { - throw GLFWException(); - } + _mainWindow = createRef(_engineConfig.windowWidth, _engineConfig.windowHeight, _engineConfig.windowTitle); - _window.resize(_engineConfig.window_width, _engineConfig.window_height); - _window.setTitle(_engineConfig.window_title); + _renderer = gfx::Renderer::create(_engineConfig.renderingApi); + _renderer->init(_mainWindow); + _renderer->makeCurrent(); - _renderer.init(); + _renderer->setClearColor(_engineConfig.backgroundColor); - _application->init(); + _sceneManager = scene::SceneManager::get(); - // if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress)) { - // throw GladException(); - // } - // - // AssimpInitializer::init(); + _application->init(); Logger::info("Engine initialization successful"); } -void Engine::_terminate() { +void Engine::_shutdown() { + Logger::info("Engine shutingdown"); glfwTerminate(); + + if (_renderer) { + Logger::info("Shutingdown renderer"); + _renderer->shutdown(); + _renderer = nullptr; + } else { + Logger::info("Renderer is already shutdown"); + } + + Logger::info("Engine shutdown"); } } // namespace arch diff --git a/src/Window.cpp b/src/Window.cpp index ebbc0a3..148f012 100644 --- a/src/Window.cpp +++ b/src/Window.cpp @@ -2,25 +2,26 @@ #include "exceptions/GLFWException.h" #include -#include namespace arch { Window::Window(int width, int height, const std::string& name, GLFWmonitor* monitor, const Window& share) { _title = name; - initialize(width, height, _title.c_str(), monitor, share._window); + _initialize(width, height, _title.c_str(), monitor, share._window); } Window::Window(int width, int height, const std::string& name, GLFWmonitor* monitor) { _title = name; - initialize(width, height, _title.c_str(), monitor, nullptr); + _initialize(width, height, _title.c_str(), monitor, nullptr); } -void Window::initialize(int width, int height, const char* name, GLFWmonitor* monitor, GLFWwindow* window) { +void Window::_initialize(int width, int height, const char* name, GLFWmonitor* monitor, GLFWwindow* window) { if (!glfwInit()) { throw GLFWException(); } + glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API); + _window = glfwCreateWindow(width, height, name, monitor, window); if (!_window) { @@ -29,7 +30,9 @@ void Window::initialize(int width, int height, const char* name, GLFWmonitor* mo } glfwMakeContextCurrent(_window); - glfwSetFramebufferSizeCallback(_window, framebuffer_size_callback); + glfwSetFramebufferSizeCallback(_window, [](GLFWwindow* window, int width, int height) { + + }); } void Window::resize(int width, int height) const { @@ -44,10 +47,6 @@ GLFWwindow* Window::get() const { return _window; } -void Window::framebuffer_size_callback(GLFWwindow* window, int width, int height) { - glViewport(0, 0, width, height); -} - void Window::swapBuffers() const { glfwSwapBuffers(_window); } @@ -56,14 +55,4 @@ bool Window::shouldClose() const { return glfwWindowShouldClose(_window); } -void Window::clear(Color color) const { - glClearColor(color.r, color.g, color.b, color.a); - glClear(GL_COLOR_BUFFER_BIT); -} - -void Window::clear(float r, float g, float b, float a) const { - Color color(r, g, b, a); - clear(color); -} - } // namespace arch diff --git a/src/gfx/Mesh.cpp b/src/gfx/Mesh.cpp new file mode 100644 index 0000000..4ad6e1f --- /dev/null +++ b/src/gfx/Mesh.cpp @@ -0,0 +1,17 @@ +#include "gfx/Mesh.h" + +#include "gfx/Buffer.h" + +namespace arch::gfx { + +Mesh::Mesh(const Ref& vertexBuffer, const Ref& indexBuffer): + _vertexBuffer(vertexBuffer), + _indexBuffer(indexBuffer) {} + +Mesh::~Mesh() {} + +Ref Mesh::create(Ref vertexBuffer, Ref indexBuffer) { + return createRef(vertexBuffer, indexBuffer); +} + +} // namespace arch::gfx diff --git a/src/gfx/Renderer.cpp b/src/gfx/Renderer.cpp index e455c9b..b2c3f53 100644 --- a/src/gfx/Renderer.cpp +++ b/src/gfx/Renderer.cpp @@ -1,72 +1,40 @@ #include "gfx/Renderer.h" -/// @brief Temporary code - -#include -#include -#include -#include - +#include "../platform/nvrhi/NvrhiRenderer.h" +#include "../platform/vulkan/VulkanRenderer.h" #include "Logger.h" namespace arch::gfx { -void Renderer::init() { - volkInitialize(); - - VkApplicationInfo appInfo{ - .sType = VK_STRUCTURE_TYPE_APPLICATION_INFO, - .pApplicationName = "Hello Triangle", - .applicationVersion = VK_MAKE_VERSION(1, 0, 0), - .pEngineName = "No Engine", - .engineVersion = VK_MAKE_VERSION(1, 0, 0), - .apiVersion = VK_API_VERSION_1_3, - }; - - std::array instanceExtensions = { - VK_KHR_SURFACE_EXTENSION_NAME, - }; - - std::vector layers = { - "VK_LAYER_KHRONOS_validation", - }; +Ref Renderer::s_current = nullptr; - uint32_t layerCount; - vkEnumerateInstanceLayerProperties(&layerCount, nullptr); - std::vector availableLayers(layerCount); - vkEnumerateInstanceLayerProperties(&layerCount, availableLayers.data()); +Ref Renderer::create(RenderingAPI api) { + switch (api) { + case RenderingAPI::vulkan: return createRef(); - std::erase_if(layers, [&](auto&& layer) { - for (auto&& l : availableLayers) { - if (std::strcmp(layer, l.layerName) == 0) { - return false; - } - } - return true; - }); + case RenderingAPI::Nvrhi_DX11: + case RenderingAPI::Nvrhi_DX12: + case RenderingAPI::Nvrhi_VK: return createRef(api, true); - Logger::info("Available layers {}:", layerCount); - for (auto&& layer : availableLayers) { - Logger::info(" - {}", layer.layerName); + default: Logger::critical("Unknown RenderingAPI {}", (u32)api); return nullptr; } + return nullptr; +} - VkInstanceCreateInfo createInfo{ - .sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO, - .pApplicationInfo = &appInfo, - .enabledLayerCount = (uint32_t)layers.size(), - .ppEnabledLayerNames = layers.data(), - .enabledExtensionCount = (uint32_t)instanceExtensions.size(), - .ppEnabledExtensionNames = instanceExtensions.data(), - }; +Ref Renderer::getCurrent() { + if (!s_current) { + Logger::critical("[Renderer] Current context is null"); + } - VkInstance instance; - VkResult status = vkCreateInstance(&createInfo, nullptr, &instance); + return s_current; +} - if (status != VK_SUCCESS) { - Logger::error("Failed to create Vulkan instance."); - } +void Renderer::makeCurrent() { + s_current = shared_from_this(); +} - Logger::info("Created Vulkan instance."); +void Renderer::setClearColor(Color color) { + _clearColor = color; } } // namespace arch::gfx diff --git a/src/gfx/buffer/BufferManager.cpp b/src/gfx/buffer/BufferManager.cpp new file mode 100644 index 0000000..598e56d --- /dev/null +++ b/src/gfx/buffer/BufferManager.cpp @@ -0,0 +1,25 @@ +#include "gfx/buffer/BufferManager.h" + +namespace arch::gfx::buffer { + +Ref BufferManager::createVertexBuffer() { + return _createVertexBufferImpl(nullptr, 0); +} + +Ref BufferManager::createVertexBuffer(void* data, u32 size) { + return _createVertexBufferImpl(data, size); +} + +Ref BufferManager::createIndexBuffer() { + return _createIndexBufferImpl(std::span((u32*)nullptr, 0)); +} + +Ref BufferManager::createIndexBuffer(std::span indices) { + return _createIndexBufferImpl(indices); +} + +Ref BufferManager::createBuffer() { + return _createBufferImpl(nullptr, 0); +} + +} // namespace arch::gfx::buffer diff --git a/src/gfx/texture/Texture.cpp b/src/gfx/texture/Texture.cpp new file mode 100644 index 0000000..10cf8c0 --- /dev/null +++ b/src/gfx/texture/Texture.cpp @@ -0,0 +1,51 @@ +#include "gfx/texture/Texture.h" + +namespace arch::gfx::texture { + +Texture::Texture(GraphicsFormat format, TextureWrapMode wrapMode, TextureFilterMode filterMode, bool isReadable): + _isReadable(isReadable), + _format(format), + _wrapMode(wrapMode), + _filterMode(filterMode) {} + +TextureFilterMode Texture::getFilter() const { + return _filterMode; +} + +void Texture::setFilter(TextureFilterMode filterMode) { + _filterMode = filterMode; +} + +TextureWrapMode Texture::getWrap() const { + return _wrapMode; +} + +void Texture::setWrap(TextureWrapMode wrapMode) { + _wrapMode = wrapMode; +} + +GraphicsFormat Texture::getFormat() const { + return _format; +} + +bool Texture::isReadable() const { + return _isReadable; +} + +u32 Texture::getWidth() const { + return getSize().x; +} + +u32 Texture::getHeight() const { + return getSize().y; +} + +u32 Texture::getDepth() const { + return getSize().z; +} + +u32 Texture::getSize(u32 axis) const { + return getSize()[axis]; +} + +} // namespace arch::gfx::texture diff --git a/src/gfx/texture/TextureManager.cpp b/src/gfx/texture/TextureManager.cpp new file mode 100644 index 0000000..a0ad0de --- /dev/null +++ b/src/gfx/texture/TextureManager.cpp @@ -0,0 +1,29 @@ +#include "gfx/texture/TextureManager.h" + +namespace arch::gfx::texture { + +Ref TextureManager::createTexture2D( + u32 width, + u32 height, + Color* pixels, + GraphicsFormat format, + TextureWrapMode wrapMode, + TextureFilterMode filterMode, + bool isReadable +) { + return _createTexture2DImpl(width, height, format, pixels, wrapMode, filterMode, isReadable); +} + +Ref TextureManager::createTexture2D( + u32 width, + u32 height, + GraphicsFormat format, + void* pixels, + TextureWrapMode wrapMode, + TextureFilterMode filterMode, + bool isReadable +) { + return _createTexture2DImpl(width, height, format, pixels, wrapMode, filterMode, isReadable); +} + +} // namespace arch::gfx::texture diff --git a/src/platform/nvrhi/NvhriRenderer.cpp b/src/platform/nvrhi/NvhriRenderer.cpp new file mode 100644 index 0000000..c556e61 --- /dev/null +++ b/src/platform/nvrhi/NvhriRenderer.cpp @@ -0,0 +1,274 @@ +#include + +#include "Logger.h" +#include "NvrhiRenderer.h" +#include "buffer/NvrhiBufferManager.h" +#include "context/NvrhiVulkanContext.h" +#include "nvrhi/utils.h" +#include "nvrhi/validation.h" +#include "texture/NvrhiTextureManager.h" + +namespace arch::gfx::nvrhi { + +struct Vertex { + float3 position; + float2 uv; +}; + +static const Vertex g_Vertices[] = { + { { -.25f, -.25f, 0.1f }, { 0.f, 0.f } }, + { { 0.f, .25f, 0.1f }, { .5f, 1.f } }, + { { .25f, -.25f, 0.1f }, { 1.f, 0.f } }, +}; + +constexpr std::string_view vertexShader = R"( +#version 450 + +layout(location = 0) in vec3 inPosition; +layout(location = 1) in vec2 inUV; + +layout(location = 0) out vec3 fragColor; + +layout(binding = 256) uniform UniformBufferObject { + mat4 mvp; +} ubo; + + +void main() { + gl_Position = ubo.mvp * vec4(inPosition, 1.0); + fragColor = vec3(inUV, 0.0); +} +)"; + +constexpr std::string_view fragmentShader = R"( +#version 450 + +layout(location = 0) in vec3 fragColor; + +layout(location = 0) out vec4 outColor; + +void main() { + outColor = vec4(fragColor, 1.0); +} +)"; + +::nvrhi::GraphicsPipelineHandle s_pipeline; +::nvrhi::BindingLayoutHandle s_bindingLayout; +::nvrhi::BindingSetHandle s_bindingSet; + +::nvrhi::BufferHandle s_constantBuffer; +::nvrhi::BufferHandle s_vertexBuffer; + +::nvrhi::CommandListHandle s_commandList; + +NvrhiRenderer::NvrhiRenderer(RenderingAPI api, bool debug): Renderer(api, debug) { + _bufferManager = createRef(); + // _textureManager = createRef(); +} + +NvrhiRenderer::~NvrhiRenderer() { + if (_context) { + shutdown(); + } +} + +void NvrhiRenderer::init(const Ref& window) { + switch (_api) { + case RenderingAPI::Nvrhi_VK: + { + _context = createRef(_debug); + break; + } + // case RenderingAPI::Nvrhi_DX11: break; + // case RenderingAPI::Nvrhi_DX12: break; + default: Logger::error("Invalid rendering API"); break; + } + _context->init(window); + + if (_debug) { + _validationLayer = ::nvrhi::validation::createValidationLayer(_context->getDevice()); + } + + { + shaderc::CompileOptions options; + shaderc::Compiler compiler; + auto result = compiler.CompileGlslToSpv( + vertexShader.data(), + vertexShader.size(), + shaderc_glsl_vertex_shader, + "shader.vert.spv", + options + ); + + if (result.GetCompilationStatus() != shaderc_compilation_status_success) { + throw vulkan::exceptions::VulkanException(result.GetErrorMessage()); + } + + ::nvrhi::ShaderHandle vertexShader = getDevice()->createShader( + ::nvrhi::ShaderDesc(::nvrhi::ShaderType::Vertex), + result.begin(), + (u64)std::distance(result.begin(), result.end()) * sizeof(u32) + ); + + ::nvrhi::VertexAttributeDesc attributes[] = { ::nvrhi::VertexAttributeDesc() + .setName("POSITION") + .setFormat(::nvrhi::Format::RGB32_FLOAT) + .setOffset(0) + .setElementStride(sizeof(float) * 5), + ::nvrhi::VertexAttributeDesc() + .setName("TEXCOORD") + .setFormat(::nvrhi::Format::RG32_FLOAT) + .setOffset(sizeof(float) * 3) + .setElementStride(sizeof(float) * 5) }; + + ::nvrhi::InputLayoutHandle inputLayout = + getDevice()->createInputLayout(attributes, uint32_t(std::size(attributes)), vertexShader); + + result = compiler.CompileGlslToSpv( + fragmentShader.data(), + fragmentShader.size(), + shaderc_glsl_fragment_shader, + "shader.vert.spv", + options + ); + ::nvrhi::ShaderHandle pixelShader = getDevice()->createShader( + ::nvrhi::ShaderDesc(::nvrhi::ShaderType::Pixel), + result.begin(), + (u64)std::distance(result.begin(), result.end()) * sizeof(u32) + ); + + auto constantBufferDesc = ::nvrhi::BufferDesc() + .setByteSize(sizeof(Mat4x4)) // stores one matrix + .setIsConstantBuffer(true) + .setIsVolatile(true) + .setMaxVersions(16); // number of automatic versions, only necessary on Vulkan + + s_constantBuffer = getDevice()->createBuffer(constantBufferDesc); + + auto vertexBufferDesc = ::nvrhi::BufferDesc() + .setByteSize(sizeof(g_Vertices)) + .setIsVertexBuffer(true) + .setInitialState(::nvrhi::ResourceStates::VertexBuffer) + .setKeepInitialState(true) // enable fully automatic state tracking + .setDebugName("Vertex Buffer"); + + s_vertexBuffer = getDevice()->createBuffer(vertexBufferDesc); + + s_commandList = getDevice()->createCommandList(); + + auto bindingSetDesc = ::nvrhi::BindingSetDesc() + // .addItem(::nvrhi::BindingSetItem::Texture_SRV(0, geometryTexture)) + .addItem(::nvrhi::BindingSetItem::ConstantBuffer(0, s_constantBuffer)); + + ::nvrhi::utils::CreateBindingSetAndLayout( + getDevice(), + ::nvrhi::ShaderType::All, + 0, + bindingSetDesc, + s_bindingLayout, + s_bindingSet + ); + + auto pipelineDesc = ::nvrhi::GraphicsPipelineDesc() + .setInputLayout(inputLayout) + .setVertexShader(vertexShader) + .setPixelShader(pixelShader) + .addBindingLayout(s_bindingLayout); + + pipelineDesc.renderState.depthStencilState.depthTestEnable = false; + + s_pipeline = getDevice()->createGraphicsPipeline(pipelineDesc, _context->getFramebuffer(0)); + + s_commandList->open(); + + s_commandList->writeBuffer(s_vertexBuffer, g_Vertices, sizeof(g_Vertices)); + + s_commandList->close(); + getDevice()->executeCommandList(s_commandList); + } +} + +void NvrhiRenderer::shutdown() { + s_pipeline = nullptr; + s_bindingLayout = nullptr; + s_bindingSet = nullptr; + + s_constantBuffer = nullptr; + s_vertexBuffer = nullptr; + s_commandList = nullptr; + + _validationLayer = nullptr; + + if (_context.use_count() > 1) { + Logger::warn("NVRHI context is still in use."); + } + _context->shutdown(); + _context.reset(); +} + +void NvrhiRenderer::onResize(u32 width, u32 height) { + _context->onResize(width, height); +} + +void NvrhiRenderer::beginFrame() { + _context->beginFrame(); + + s_commandList->open(); + + ::nvrhi::utils::ClearColorAttachment( + s_commandList, + _context->getFramebuffer(_context->getCurrentFrameIndex()), + 0, + ::nvrhi::Color(_clearColor.r, _clearColor.g, _clearColor.b, _clearColor.a) + ); +} + +void NvrhiRenderer::present() { + s_commandList->close(); + getDevice()->executeCommandList(s_commandList); + + _context->present(); +} + +void NvrhiRenderer::render(const Ref& mesh, const Mat4x4& transform) { + ::nvrhi::FramebufferHandle currentFramebuffer = _context->getFramebuffer(_context->getCurrentFrameIndex()); + + s_commandList->writeBuffer(s_constantBuffer, &transform, sizeof(transform)); + + ::nvrhi::VertexBufferBinding vbufBinding = + ::nvrhi::VertexBufferBinding().setBuffer(s_vertexBuffer).setSlot(0).setOffset(0); + + uint2 framebufferSize = _context->getFramebufferSize(); + auto graphicsState = ::nvrhi::GraphicsState() + .setPipeline(s_pipeline) + .setFramebuffer(currentFramebuffer) + .setViewport( + ::nvrhi::ViewportState().addViewportAndScissorRect( + ::nvrhi::Viewport(framebufferSize.x, framebufferSize.y) + ) + ) + .addBindingSet(s_bindingSet) + .addVertexBuffer(vbufBinding); + s_commandList->setGraphicsState(graphicsState); + + auto drawArguments = ::nvrhi::DrawArguments().setVertexCount(3); + s_commandList->draw(drawArguments); +} + +Ref NvrhiRenderer::getBufferManager() { + return _bufferManager; +} + +Ref NvrhiRenderer::getTextureManager() { + return _textureManager; +} + +::nvrhi::DeviceHandle NvrhiRenderer::getDevice() { + if (_validationLayer) { + return _validationLayer; + } + + return _context->getDevice(); +} + +} // namespace arch::gfx::nvrhi diff --git a/src/platform/nvrhi/NvrhiContext.cpp b/src/platform/nvrhi/NvrhiContext.cpp new file mode 100644 index 0000000..a37cbef --- /dev/null +++ b/src/platform/nvrhi/NvrhiContext.cpp @@ -0,0 +1,46 @@ +#include "NvrhiContext.h" + +namespace arch::gfx::nvrhi { + +NvrhiContext::~NvrhiContext() { + NvrhiContext::shutdown(); +} + +void NvrhiContext::shutdown() { + _depthStencilBuffer = nullptr; + _framebuffers.clear(); +} + +::nvrhi::FramebufferHandle NvrhiContext::getFramebuffer(int index) { + return _framebuffers[index]; +} + +void NvrhiContext::_preResizeFramebuffers() { + _framebuffers.clear(); + _depthStencilBuffer = nullptr; +} + +void NvrhiContext::_postResizeFramebuffers() { + auto backBuffer = _getBackBuffer(0); + _depthStencilBuffer = getDevice()->createTexture( + ::nvrhi::TextureDesc() + .setFormat(::nvrhi::Format::D32) + .setWidth(backBuffer->getDesc().width) + .setHeight(backBuffer->getDesc().height) + .setDepth(1) + .setArraySize(1) + .setMipLevels(1) + .setIsRenderTarget(true) + .setDebugName("DepthStencilBuffer") + ); + + u32 backBufferCount = _getBackBufferCount(); + _framebuffers.resize(backBufferCount); + for (uint32_t index = 0; index < backBufferCount; index++) { + _framebuffers[index] = getDevice()->createFramebuffer( + ::nvrhi::FramebufferDesc().addColorAttachment(_getBackBuffer(index)).setDepthAttachment(_depthStencilBuffer) + ); + } +} + +} // namespace arch::gfx::nvrhi diff --git a/src/platform/nvrhi/NvrhiContext.h b/src/platform/nvrhi/NvrhiContext.h new file mode 100644 index 0000000..3de54a5 --- /dev/null +++ b/src/platform/nvrhi/NvrhiContext.h @@ -0,0 +1,50 @@ +#pragma once + +#include "ArchMath.h" +#include "Ref.h" +#include "nvrhi/nvrhi.h" + +namespace arch { +class Window; +} + +namespace arch::gfx::nvrhi { + +class NvrhiContext { +public: + NvrhiContext() = default; + virtual ~NvrhiContext(); + + virtual void init(const Ref& window) = 0; + virtual void shutdown(); + + virtual void onResize(u32 width, u32 height) = 0; + + virtual void beginFrame() = 0; + virtual void present() = 0; + +public: + ::nvrhi::FramebufferHandle getFramebuffer(int index); + +public: + virtual int getCurrentFrameIndex() = 0; + virtual uint2 getFramebufferSize() const = 0; + + virtual ::nvrhi::DeviceHandle getDevice() = 0; + +protected: + void _preResizeFramebuffers(); + void _postResizeFramebuffers(); + +protected: + virtual u32 _getBackBufferCount() const = 0; + virtual ::nvrhi::TextureHandle _getBackBuffer(uint32_t index) const = 0; + +protected: + bool _vsync = false; + std::vector<::nvrhi::FramebufferHandle> _framebuffers; + + ::nvrhi::TextureHandle _depthStencilBuffer; +}; + +} // namespace arch::gfx::nvrhi diff --git a/src/platform/nvrhi/NvrhiMessageCallback.h b/src/platform/nvrhi/NvrhiMessageCallback.h new file mode 100644 index 0000000..32bbb26 --- /dev/null +++ b/src/platform/nvrhi/NvrhiMessageCallback.h @@ -0,0 +1,28 @@ +#pragma once + +#include "Logger.h" +#include "nvrhi/nvrhi.h" + +namespace arch::gfx::nvrhi { + +struct MessageCallback: ::nvrhi::IMessageCallback { + void message(::nvrhi::MessageSeverity severity, const char* message) override { + LogLevel level = LogLevel::debug; + + switch (severity) { + case ::nvrhi::MessageSeverity::Fatal: level = LogLevel::critical; break; + case ::nvrhi::MessageSeverity::Error: level = LogLevel::error; break; + case ::nvrhi::MessageSeverity::Warning: level = LogLevel::warn; break; + case ::nvrhi::MessageSeverity::Info: level = LogLevel::info; break; + } + + Logger::log(level, "[Nvrhi]: {}", message); + } + + static MessageCallback* GetInstance() { + static MessageCallback instance; + return &instance; + } +}; + +} // namespace arch::gfx::nvrhi diff --git a/src/platform/nvrhi/NvrhiRenderer.h b/src/platform/nvrhi/NvrhiRenderer.h new file mode 100644 index 0000000..2f00f3f --- /dev/null +++ b/src/platform/nvrhi/NvrhiRenderer.h @@ -0,0 +1,47 @@ +#pragma once +#include "NvrhiContext.h" +#include "gfx/Renderer.h" +#include + +namespace arch::gfx::nvrhi { + +namespace buffer { +class NvrhiBufferManager; +} + +namespace texture { +class NvrhiTextureManager; +} + +class NvrhiRenderer final: public Renderer { +public: + NvrhiRenderer(RenderingAPI api, bool debug = true); + ~NvrhiRenderer() override; + + void init(const Ref& window) override; + void shutdown() override; + +public: + void onResize(u32 width, u32 height) override; + + void beginFrame() override; + void present() override; + + void render(const Ref& mesh, const Mat4x4& transform) override; + +public: + Ref getBufferManager() override; + Ref getTextureManager() override; + +public: + ::nvrhi::DeviceHandle getDevice(); + +private: + Ref _context; + ::nvrhi::DeviceHandle _validationLayer; + + Ref _bufferManager; + Ref _textureManager; +}; + +} // namespace arch::gfx::nvrhi diff --git a/src/platform/nvrhi/NvrhiUtils.cpp b/src/platform/nvrhi/NvrhiUtils.cpp new file mode 100644 index 0000000..e861e08 --- /dev/null +++ b/src/platform/nvrhi/NvrhiUtils.cpp @@ -0,0 +1,62 @@ +#include "NvrhiUtils.h" + +namespace arch::gfx::nvrhi { + +::nvrhi::Format NvrhiUtils::getFormat(GraphicsFormat format) { + switch (format) { + default: return ::nvrhi::Format::UNKNOWN; + case GraphicsFormat::r8: return ::nvrhi::Format::R8_UINT; + case GraphicsFormat::r8s: return ::nvrhi::Format::R8_SINT; + case GraphicsFormat::r8_norm: return ::nvrhi::Format::R8_UNORM; + case GraphicsFormat::r8s_norm: return ::nvrhi::Format::R8_SNORM; + case GraphicsFormat::r16: return ::nvrhi::Format::R16_UINT; + case GraphicsFormat::r16s: return ::nvrhi::Format::R16_SINT; + case GraphicsFormat::r16_norm: return ::nvrhi::Format::R16_UNORM; + case GraphicsFormat::r16s_norm: return ::nvrhi::Format::R16_SNORM; + case GraphicsFormat::r16f: return ::nvrhi::Format::R16_FLOAT; + case GraphicsFormat::rg8: return ::nvrhi::Format::RG8_UINT; + case GraphicsFormat::rg8s: return ::nvrhi::Format::RG8_SINT; + case GraphicsFormat::rg8_norm: return ::nvrhi::Format::RG8_UNORM; + case GraphicsFormat::rg8s_norm: return ::nvrhi::Format::RG8_SNORM; + case GraphicsFormat::bgra4_norm: return ::nvrhi::Format::BGRA4_UNORM; + case GraphicsFormat::b5g6r5_norm: return ::nvrhi::Format::B5G6R5_UNORM; + case GraphicsFormat::b5g5r5a1_norm: return ::nvrhi::Format::B5G5R5A1_UNORM; + case GraphicsFormat::rgba8: return ::nvrhi::Format::RGBA8_UINT; + case GraphicsFormat::rgba8s: return ::nvrhi::Format::RGBA8_SINT; + case GraphicsFormat::rgba8_norm: return ::nvrhi::Format::RGBA8_UNORM; + case GraphicsFormat::rgba8s_norm: return ::nvrhi::Format::RGBA8_SNORM; + case GraphicsFormat::bgra8_norm: return ::nvrhi::Format::BGRA8_UNORM; + case GraphicsFormat::srgba8_norm: return ::nvrhi::Format::SRGBA8_UNORM; + case GraphicsFormat::sbgra8_norm: return ::nvrhi::Format::SBGRA8_UNORM; + case GraphicsFormat::r10g10b10a2_norm: return ::nvrhi::Format::R10G10B10A2_UNORM; + case GraphicsFormat::r11g11b10f: return ::nvrhi::Format::R11G11B10_FLOAT; + case GraphicsFormat::rg16: return ::nvrhi::Format::RG16_UINT; + case GraphicsFormat::rg16s: return ::nvrhi::Format::RG16_SINT; + case GraphicsFormat::rg16_norm: return ::nvrhi::Format::RG16_UNORM; + case GraphicsFormat::rg16s_norm: return ::nvrhi::Format::RG16_SNORM; + case GraphicsFormat::rg16f: return ::nvrhi::Format::RG16_FLOAT; + case GraphicsFormat::r32: return ::nvrhi::Format::R32_UINT; + case GraphicsFormat::r32s: return ::nvrhi::Format::R32_SINT; + case GraphicsFormat::r32f: return ::nvrhi::Format::R32_FLOAT; + case GraphicsFormat::rgba16: return ::nvrhi::Format::RGBA16_UINT; + case GraphicsFormat::rgba16s: return ::nvrhi::Format::RGBA16_SINT; + case GraphicsFormat::rgba16f: return ::nvrhi::Format::RGBA16_FLOAT; + case GraphicsFormat::rgba16_norm: return ::nvrhi::Format::RGBA16_UNORM; + case GraphicsFormat::rgba16s_norm: return ::nvrhi::Format::RGBA16_SNORM; + case GraphicsFormat::rg32: return ::nvrhi::Format::RG32_UINT; + case GraphicsFormat::rg32s: return ::nvrhi::Format::RG32_SINT; + case GraphicsFormat::rg32f: return ::nvrhi::Format::RG32_FLOAT; + case GraphicsFormat::rgb32: return ::nvrhi::Format::RGB32_UINT; + case GraphicsFormat::rgb32s: return ::nvrhi::Format::RGB32_SINT; + case GraphicsFormat::rgb32f: return ::nvrhi::Format::RGB32_FLOAT; + case GraphicsFormat::rgba32: return ::nvrhi::Format::RGBA32_UINT; + case GraphicsFormat::rgba32s: return ::nvrhi::Format::RGBA32_SINT; + case GraphicsFormat::rgba32f: return ::nvrhi::Format::RGBA32_FLOAT; + case GraphicsFormat::depth16: return ::nvrhi::Format::D16; + case GraphicsFormat::depth24Stencil8: return ::nvrhi::Format::D24S8; + case GraphicsFormat::depth32f: return ::nvrhi::Format::D32; + case GraphicsFormat::depth32fStencil8: return ::nvrhi::Format::D32S8; + } +} + +} // namespace arch::gfx::nvrhi diff --git a/src/platform/nvrhi/NvrhiUtils.h b/src/platform/nvrhi/NvrhiUtils.h new file mode 100644 index 0000000..01454e6 --- /dev/null +++ b/src/platform/nvrhi/NvrhiUtils.h @@ -0,0 +1,12 @@ +#pragma once +#include "gfx/GraphicsFormat.h" +#include "nvrhi/nvrhi.h" + +namespace arch::gfx::nvrhi { + +class NvrhiUtils { +public: + static ::nvrhi::Format getFormat(GraphicsFormat format); +}; + +} // namespace arch::gfx::nvrhi diff --git a/src/platform/nvrhi/buffer/NvrhiBuffer.cpp b/src/platform/nvrhi/buffer/NvrhiBuffer.cpp new file mode 100644 index 0000000..8f6115a --- /dev/null +++ b/src/platform/nvrhi/buffer/NvrhiBuffer.cpp @@ -0,0 +1,11 @@ +#include "NvrhiBuffer.h" + +namespace arch::gfx::nvrhi::buffer { + +NvrhiBuffer::NvrhiBuffer(gfx::buffer::BufferType type): Buffer(type) {} + +NvrhiBuffer::~NvrhiBuffer() {} + +void NvrhiBuffer::setData(void* data, u64 size) const {} + +} // namespace arch::gfx::nvrhi::buffer diff --git a/src/platform/nvrhi/buffer/NvrhiBuffer.h b/src/platform/nvrhi/buffer/NvrhiBuffer.h new file mode 100644 index 0000000..b41a8ea --- /dev/null +++ b/src/platform/nvrhi/buffer/NvrhiBuffer.h @@ -0,0 +1,19 @@ +#pragma once +#include "gfx/buffer/Buffer.h" +#include "nvrhi/nvrhi.h" + +namespace arch::gfx::nvrhi::buffer { + +class NvrhiBuffer: public virtual gfx::buffer::Buffer { +public: + NvrhiBuffer(gfx::buffer::BufferType type); + ~NvrhiBuffer() override; + +public: + void setData(void* data, u64 size) const override; + +private: + ::nvrhi::BufferHandle _buffer; +}; + +} // namespace arch::gfx::nvrhi::buffer diff --git a/src/platform/nvrhi/buffer/NvrhiBufferManager.cpp b/src/platform/nvrhi/buffer/NvrhiBufferManager.cpp new file mode 100644 index 0000000..4931a00 --- /dev/null +++ b/src/platform/nvrhi/buffer/NvrhiBufferManager.cpp @@ -0,0 +1,20 @@ +#include "NvrhiBufferManager.h" + +#include "NvrhiIndexBuffer.h" +#include "NvrhiVertexBuffer.h" + +namespace arch::gfx::nvrhi::buffer { + +Ref NvrhiBufferManager::_createVertexBufferImpl(void* data, u32 size) { + return createRef(); +} + +Ref NvrhiBufferManager::_createIndexBufferImpl(std::span indices) { + return createRef(); +} + +Ref NvrhiBufferManager::_createBufferImpl(void* data, u32 size) { + return createRef(gfx::buffer::BufferType::blob); +} + +} // namespace arch::gfx::nvrhi::buffer diff --git a/src/platform/nvrhi/buffer/NvrhiBufferManager.h b/src/platform/nvrhi/buffer/NvrhiBufferManager.h new file mode 100644 index 0000000..6e5b116 --- /dev/null +++ b/src/platform/nvrhi/buffer/NvrhiBufferManager.h @@ -0,0 +1,17 @@ +#pragma once +#include "gfx/buffer/BufferManager.h" + +namespace arch::gfx::nvrhi::buffer { + +class NvrhiBufferManager final: public gfx::buffer::BufferManager { +public: + NvrhiBufferManager() = default; + ~NvrhiBufferManager() override = default; + +protected: + Ref _createVertexBufferImpl(void* data, u32 size) override; + Ref _createIndexBufferImpl(std::span indices) override; + Ref _createBufferImpl(void* data, u32 size) override; +}; + +} // namespace arch::gfx::nvrhi::buffer diff --git a/src/platform/nvrhi/buffer/NvrhiIndexBuffer.cpp b/src/platform/nvrhi/buffer/NvrhiIndexBuffer.cpp new file mode 100644 index 0000000..d74dc9e --- /dev/null +++ b/src/platform/nvrhi/buffer/NvrhiIndexBuffer.cpp @@ -0,0 +1,11 @@ +#include "NvrhiIndexBuffer.h" + +namespace arch::gfx::nvrhi::buffer { + +NvrhiIndexBuffer::NvrhiIndexBuffer(): + NvrhiBuffer(gfx::buffer::BufferType::vertex), + Buffer(gfx::buffer::BufferType::vertex) {} + +NvrhiIndexBuffer::~NvrhiIndexBuffer() {} + +} // namespace arch::gfx::nvrhi::buffer diff --git a/src/platform/nvrhi/buffer/NvrhiIndexBuffer.h b/src/platform/nvrhi/buffer/NvrhiIndexBuffer.h new file mode 100644 index 0000000..1d4ab73 --- /dev/null +++ b/src/platform/nvrhi/buffer/NvrhiIndexBuffer.h @@ -0,0 +1,13 @@ +#pragma once +#include "NvrhiBuffer.h" +#include "gfx/buffer/IndexBuffer.h" + +namespace arch::gfx::nvrhi::buffer { + +class NvrhiIndexBuffer final: public gfx::buffer::IndexBuffer, public NvrhiBuffer { +public: + NvrhiIndexBuffer(); + ~NvrhiIndexBuffer() override; +}; + +} // namespace arch::gfx::nvrhi::buffer diff --git a/src/platform/nvrhi/buffer/NvrhiVertexBuffer.cpp b/src/platform/nvrhi/buffer/NvrhiVertexBuffer.cpp new file mode 100644 index 0000000..73796aa --- /dev/null +++ b/src/platform/nvrhi/buffer/NvrhiVertexBuffer.cpp @@ -0,0 +1,11 @@ +#include "NvrhiVertexBuffer.h" + +namespace arch::gfx::nvrhi::buffer { + +NvrhiVertexBuffer::NvrhiVertexBuffer(): + NvrhiBuffer(gfx::buffer::BufferType::vertex), + Buffer(gfx::buffer::BufferType::vertex) {} + +NvrhiVertexBuffer::~NvrhiVertexBuffer() {} + +} // namespace arch::gfx::nvrhi::buffer diff --git a/src/platform/nvrhi/buffer/NvrhiVertexBuffer.h b/src/platform/nvrhi/buffer/NvrhiVertexBuffer.h new file mode 100644 index 0000000..f84c136 --- /dev/null +++ b/src/platform/nvrhi/buffer/NvrhiVertexBuffer.h @@ -0,0 +1,14 @@ +#pragma once +#include "NvrhiBuffer.h" +#include "gfx/buffer/VertexBuffer.h" +#include "nvrhi/nvrhi.h" + +namespace arch::gfx::nvrhi::buffer { + +class NvrhiVertexBuffer final: public gfx::buffer::VertexBuffer, public NvrhiBuffer { +public: + NvrhiVertexBuffer(); + ~NvrhiVertexBuffer() override; +}; + +} // namespace arch::gfx::nvrhi::buffer diff --git a/src/platform/nvrhi/context/NvrhiVulkanContext.cpp b/src/platform/nvrhi/context/NvrhiVulkanContext.cpp new file mode 100644 index 0000000..a56d6ed --- /dev/null +++ b/src/platform/nvrhi/context/NvrhiVulkanContext.cpp @@ -0,0 +1,369 @@ +#define NOMINMAX +#include "NvrhiVulkanContext.h" + +#include + +#include "../../vulkan/VulkanUtils.h" +#include "../NvrhiMessageCallback.h" +#include "../NvrhiUtils.h" +#include "Window.h" +#include "nvrhi/validation.h" +#include "nvrhi/vulkan.h" + +VULKAN_HPP_DEFAULT_DISPATCH_LOADER_DYNAMIC_STORAGE + +namespace arch::gfx::nvrhi { + +NvrhiVulkanContext::NvrhiVulkanContext(bool enableValidationLayers): + VulkanContext(true), + _enableValidationLayers(enableValidationLayers) {} + +NvrhiVulkanContext::~NvrhiVulkanContext() { + shutdown(); +} + +void NvrhiVulkanContext::init(const Ref& window) { + _createSurface(window); + initDevice(_surface); + + ::nvrhi::vulkan::DeviceDesc desc; + desc.instance = getInstance(); + desc.physicalDevice = getPhysicalDevice(); + desc.device = VulkanContext::getDevice(); + desc.allocationCallbacks = getAllocator(); + + Queue gQueue = getQueue(QueueType::graphics); + desc.graphicsQueue = gQueue.queue; + desc.graphicsQueueIndex = gQueue.index; + + Queue tQueue = getQueue(QueueType::transfer); + desc.transferQueue = tQueue.queue; + desc.transferQueueIndex = tQueue.index; + + Queue cQueue = getQueue(QueueType::compute); + desc.computeQueue = cQueue.queue; + desc.computeQueueIndex = cQueue.index; + + desc.errorCB = MessageCallback::GetInstance(); + + desc.instanceExtensions = const_cast(DEVICE_EXTENSIONS.data()); + desc.numInstanceExtensions = DEVICE_EXTENSIONS.size(); + + // Dynamically load the Vulkan-Hpp function pointers (used by NVRHI) + { + const vk::DynamicLoader dl; + const PFN_vkGetInstanceProcAddr vkGetInstanceProcAddr = + dl.getProcAddress("vkGetInstanceProcAddr"); + VULKAN_HPP_DEFAULT_DISPATCHER.init(desc.instance, vkGetInstanceProcAddr, desc.device); + } + + _device = createDevice(desc); + + i32 width, height; + glfwGetFramebufferSize(window->get(), &width, &height); + + onResize(width, height); + _createFrameSemaphores(); +} + +void NvrhiVulkanContext::shutdown() { + NvrhiContext::shutdown(); + + if (_swapchain) { + _destroySwapchain(); + } + + for (u32 i = 0; i < _acquireSemaphores.size(); i++) { + vkDestroySemaphore(VulkanContext::getDevice(), _acquireSemaphores[i], getAllocator()); + vkDestroySemaphore(VulkanContext::getDevice(), _presentSemaphores[i], getAllocator()); + } + _acquireSemaphores.clear(); + _presentSemaphores.clear(); + + _device = nullptr; + + if (_surface) { + vkDestroySurfaceKHR(getInstance(), _surface, getAllocator()); + _surface = nullptr; + } +} + +void NvrhiVulkanContext::onResize(u32 width, u32 height) { + _preResizeFramebuffers(); + + if (_swapchain) { + _destroySwapchain(); + } + + _supportDetails = vulkan::VulkanSwapchain::SupportDetails::getSupportDetails(getPhysicalDevice(), _surface); + _createSwapchain(width, height); + + _postResizeFramebuffers(); +} + +void NvrhiVulkanContext::beginFrame() { + VkDevice device = VulkanContext::getDevice(); + + const auto& semaphore = _acquireSemaphores[_currentAcquireSemaphore]; + + VkResult res = {}; + + constexpr int maxAttempts = 3; + for (int attempt = 0; attempt < maxAttempts; ++attempt) { + res = vkAcquireNextImageKHR( + device, + _swapchain, + std::numeric_limits::max(), // timeout + semaphore, + vk::Fence(), + &_currentFrame + ); + + if (res == VK_ERROR_OUT_OF_DATE_KHR) { + _supportDetails = vulkan::VulkanSwapchain::SupportDetails::getSupportDetails(getPhysicalDevice(), _surface); + + onResize( + _supportDetails.capabilities.currentExtent.width, + _supportDetails.capabilities.currentExtent.height + ); + } else { + break; + } + } + + _currentAcquireSemaphore = (_currentAcquireSemaphore + 1) % _acquireSemaphores.size(); + + if (res == VK_SUCCESS) { + // Schedule the wait. The actual wait operation will be submitted when the app executes any command list. + _device->queueWaitForSemaphore(::nvrhi::CommandQueue::Graphics, semaphore, 0); + } +} + +void NvrhiVulkanContext::present() { + const auto& semaphore = _presentSemaphores[_currentPresentSemaphore]; + + _device->queueSignalSemaphore(::nvrhi::CommandQueue::Graphics, semaphore, 0); + + // NVRHI buffers the semaphores and signals them when something is submitted to a queue. + // Call 'executeCommandLists' with no command lists to actually signal the semaphore. + _device->executeCommandLists(nullptr, 0); + + VkPresentInfoKHR info = {}; + info.sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR; + info.waitSemaphoreCount = 1; + info.pWaitSemaphores = &semaphore; + + info.swapchainCount = 1; + info.pSwapchains = &_swapchain; + info.pImageIndices = &_currentFrame; + + const Queue& presentQueue = getQueue(QueueType::presentaion); + + VkResult res = vkQueuePresentKHR(presentQueue.queue, &info); + if (!(res == VK_SUCCESS || res == VK_ERROR_OUT_OF_DATE_KHR)) { + throw vulkan::exceptions::VulkanException("Failed to present swapchain"); + } + + _currentPresentSemaphore = (_currentPresentSemaphore + 1) % _presentSemaphores.size(); + +#ifndef _WIN32 + if (_vsync) { + vkQueueWaitIdle(presentQueue.queue); + } +#endif + + while (_framesInFlight.size() >= _maxFramesInFlight) { + auto query = _framesInFlight.front(); + _framesInFlight.pop(); + + _device->waitEventQuery(query); + + _queryPool.push_back(query); + } + + ::nvrhi::EventQueryHandle query; + if (!_queryPool.empty()) { + query = _queryPool.back(); + _queryPool.pop_back(); + } else { + query = _device->createEventQuery(); + } + + _device->resetEventQuery(query); + _device->setEventQuery(query, ::nvrhi::CommandQueue::Graphics); + _framesInFlight.push(query); +} + +int NvrhiVulkanContext::getCurrentFrameIndex() { + return _currentFrame; +} + +uint2 NvrhiVulkanContext::getFramebufferSize() const { + return { _extent.width, _extent.height }; +} + +::nvrhi::DeviceHandle NvrhiVulkanContext::getDevice() { + return _device; +} + +u32 NvrhiVulkanContext::_getBackBufferCount() const { + return _frames.size(); +} + +::nvrhi::TextureHandle NvrhiVulkanContext::_getBackBuffer(u32 index) const { + return _frames[index].handle; +} + +void NvrhiVulkanContext::_createSurface(const Ref& window) { + vulkan::VulkanUtils::vkAssert( + glfwCreateWindowSurface(getInstance(), window->get(), getAllocator(), &_surface), + "Failed to create window surface." + ); +} + +void NvrhiVulkanContext::_createSwapchain(i32 width, i32 height) { + _surfaceFormat = _supportDetails.formats[0]; + for (const auto& availableFormat : _supportDetails.formats) { + if (availableFormat.format == VK_FORMAT_B8G8R8A8_SRGB && + availableFormat.colorSpace == VK_COLOR_SPACE_SRGB_NONLINEAR_KHR) { + _surfaceFormat = availableFormat; + break; + } + } + + VkPresentModeKHR presentMode = VK_PRESENT_MODE_FIFO_KHR; +#if false // Triple buffering - currently not supported + for (const auto& availablePresentMode : swapchainSupportDetails.presentModes) { + if (availablePresentMode == VK_PRESENT_MODE_MAILBOX_KHR) { + presentMode = availablePresentMode; + break; + } + } +#endif + + if (_supportDetails.capabilities.currentExtent.width != std::numeric_limits::max()) { + _extent = _supportDetails.capabilities.currentExtent; + } else { + _extent = { std::clamp( + (u32)width, + _supportDetails.capabilities.minImageExtent.width, + _supportDetails.capabilities.maxImageExtent.width + ), + std::clamp( + (u32)height, + _supportDetails.capabilities.minImageExtent.height, + _supportDetails.capabilities.maxImageExtent.height + ) }; + } + + u32 imageCount = _supportDetails.capabilities.minImageCount + 1; + if (_supportDetails.capabilities.maxImageCount > 0 && imageCount > _supportDetails.capabilities.maxImageCount) { + imageCount = _supportDetails.capabilities.maxImageCount; + } + + VkSwapchainCreateInfoKHR createInfo = { + .sType = VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR, + .surface = _surface, + .minImageCount = imageCount, + .imageFormat = _surfaceFormat.format, + .imageColorSpace = _surfaceFormat.colorSpace, + .imageExtent = _extent, + .imageArrayLayers = 1, + .imageUsage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT, + .preTransform = _supportDetails.capabilities.currentTransform, + .compositeAlpha = VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR, + .presentMode = presentMode, + .clipped = VK_TRUE, + }; + + std::array queueFamilyIndices = { + getQueue(QueueType::graphics).index, + getQueue(QueueType::presentaion).index, + }; + + if (queueFamilyIndices[0] != queueFamilyIndices[1]) { + createInfo.imageSharingMode = VK_SHARING_MODE_CONCURRENT; + createInfo.queueFamilyIndexCount = (u32)queueFamilyIndices.size(); + createInfo.pQueueFamilyIndices = queueFamilyIndices.data(); + } else { + createInfo.imageSharingMode = VK_SHARING_MODE_EXCLUSIVE; + createInfo.queueFamilyIndexCount = 0; // Optional + createInfo.pQueueFamilyIndices = nullptr; // Optional + } + + VkDevice device = VulkanContext::getDevice(); + VkAllocationCallbacks* allocator = getAllocator(); + + vulkan::VulkanUtils::vkAssert( + vkCreateSwapchainKHR(device, &createInfo, allocator, &_swapchain), + "Failed to create swap chain!" + ); + + vkGetSwapchainImagesKHR(device, _swapchain, &imageCount, nullptr); + std::vector swapchainImages(imageCount); + vkGetSwapchainImagesKHR(device, _swapchain, &imageCount, swapchainImages.data()); + _frames.resize(imageCount); + for (u64 i = 0; i < _frames.size(); ++i) { + _frames[i].image = swapchainImages[i]; + + GraphicsFormat fromat = vulkan::VulkanUtils::getFormat(_surfaceFormat.format); + + ::nvrhi::TextureDesc textureDesc; + textureDesc.width = width; + textureDesc.height = height; + textureDesc.format = NvrhiUtils::getFormat(fromat); + textureDesc.debugName = "Swap chain image"; + textureDesc.initialState = ::nvrhi::ResourceStates::Present; + textureDesc.keepInitialState = true; + textureDesc.isRenderTarget = true; + + _frames[i].handle = getDevice()->createHandleForNativeTexture( + ::nvrhi::ObjectTypes::VK_Image, + ::nvrhi::Object(swapchainImages[i]), + textureDesc + ); + } +} + +void NvrhiVulkanContext::_destroySwapchain() { + VkDevice device = VulkanContext::getDevice(); + + vkDeviceWaitIdle(device); + + vkDestroySwapchainKHR(device, _swapchain, getAllocator()); + + _swapchain = nullptr; + + _frames.clear(); +} + +void NvrhiVulkanContext::_createFrameSemaphores() { + VkDevice device = VulkanContext::getDevice(); + + _acquireSemaphores.reserve(_maxFramesInFlight + 1); + _presentSemaphores.reserve(_maxFramesInFlight + 1); + for (u32 i = 0; i < _maxFramesInFlight + 1; i++) { + VkSemaphoreCreateInfo semaphoreInfo{}; + semaphoreInfo.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO; + + VkSemaphore acquireSemaphore; + vkCreateSemaphore(device, &semaphoreInfo, getAllocator(), &acquireSemaphore); + + VkSemaphore presentSemaphore; + vkCreateSemaphore(device, &semaphoreInfo, getAllocator(), &presentSemaphore); + + _acquireSemaphores.push_back(acquireSemaphore); + _presentSemaphores.push_back(presentSemaphore); + } +} + +void NvrhiVulkanContext::_destroyFrameSemaphores() { + for (u32 i = 0; i < _acquireSemaphores.size(); i++) { + vkDestroySemaphore(VulkanContext::getDevice(), _acquireSemaphores[i], getAllocator()); + vkDestroySemaphore(VulkanContext::getDevice(), _presentSemaphores[i], getAllocator()); + } + _acquireSemaphores.clear(); + _presentSemaphores.clear(); +} + +} // namespace arch::gfx::nvrhi diff --git a/src/platform/nvrhi/context/NvrhiVulkanContext.h b/src/platform/nvrhi/context/NvrhiVulkanContext.h new file mode 100644 index 0000000..f31cce2 --- /dev/null +++ b/src/platform/nvrhi/context/NvrhiVulkanContext.h @@ -0,0 +1,75 @@ +#pragma once + +#include + +#include "../../vulkan/VulkanContext.h" +#include "../NvrhiContext.h" +#include + +namespace arch::gfx::nvrhi { + +class NvrhiVulkanContext final: public vulkan::VulkanContext, public NvrhiContext { +public: + explicit NvrhiVulkanContext(bool enableValidationLayers = true); + ~NvrhiVulkanContext() override; + + void init(const Ref& window) override; + void shutdown() override; + + void onResize(u32 width, u32 height) override; + + void beginFrame() override; + void present() override; + +public: + int getCurrentFrameIndex() override; + uint2 getFramebufferSize() const override; + + ::nvrhi::DeviceHandle getDevice() override; + +public: + struct Frame { + VkImage image; + ::nvrhi::TextureHandle handle; + }; + +protected: + u32 _getBackBufferCount() const override; + ::nvrhi::TextureHandle _getBackBuffer(uint32_t index) const override; + +private: + void _createSurface(const Ref& window); + void _createSwapchain(i32 width, i32 height); + void _destroySwapchain(); + void _createFrameSemaphores(); + void _destroyFrameSemaphores(); + +private: + bool _enableValidationLayers; + + ::nvrhi::vulkan::DeviceHandle _device; + + VkSurfaceKHR _surface = nullptr; + VkSwapchainKHR _swapchain = nullptr; + + VkPresentModeKHR _presentMode = {}; + VkSurfaceFormatKHR _surfaceFormat = {}; + VkExtent2D _extent = { 0, 0 }; + + uint32_t _currentFrame = -1; + std::vector _frames; + + uint32_t _currentAcquireSemaphore = 0; + uint32_t _currentPresentSemaphore = 0; + std::vector _acquireSemaphores; + std::vector _presentSemaphores; + + vulkan::VulkanSwapchain::SupportDetails _supportDetails; + + std::queue<::nvrhi::EventQueryHandle> _framesInFlight; + std::vector<::nvrhi::EventQueryHandle> _queryPool; + + const int _maxFramesInFlight = 2; +}; + +} // namespace arch::gfx::nvrhi diff --git a/src/platform/nvrhi/texture/NvrhiTextureManager.h b/src/platform/nvrhi/texture/NvrhiTextureManager.h new file mode 100644 index 0000000..40b515c --- /dev/null +++ b/src/platform/nvrhi/texture/NvrhiTextureManager.h @@ -0,0 +1,23 @@ +#pragma once +#include "gfx/texture/TextureManager.h" + +namespace arch::gfx::nvrhi::texture { + +class NvrhiTextureManager final: public gfx::texture::TextureManager { +public: + NvrhiTextureManager() = default; + ~NvrhiTextureManager() override = default; + +protected: + Ref _createTexture2DImpl( + u32 width, + u32 height, + GraphicsFormat format, + void* data, + gfx::texture::TextureWrapMode wrapMode, + gfx::texture::TextureFilterMode filterMode, + bool isReadable + ) override; +}; + +} // namespace arch::gfx::nvrhi::texture diff --git a/src/platform/vulkan/VulkanContext.cpp b/src/platform/vulkan/VulkanContext.cpp new file mode 100644 index 0000000..638e35b --- /dev/null +++ b/src/platform/vulkan/VulkanContext.cpp @@ -0,0 +1,436 @@ +#include "VulkanContext.h" + +#include "VulkanUtils.h" +#include "exceptions/VulkanException.h" +#include +#include + +namespace arch::gfx::vulkan { + +VulkanContext::VulkanContext(bool enableValidationLayers): _enableValidationLayers(enableValidationLayers) { + VulkanUtils::vkAssert(volkInitialize(), "Failed to initialize volk."); + + _createInstance(); + + volkLoadInstance(_instance); + + if (_enableValidationLayers) { + _setupDebugMessage(); + } +} + +VulkanContext::~VulkanContext() { + if (_device) { + vkDestroyDevice(_device, _allocator); + _device = nullptr; + } + + if (_debugMessenger) { + vkDestroyDebugUtilsMessengerEXT(_instance, _debugMessenger, _allocator); + _debugMessenger = nullptr; + } + + if (_instance) { + vkDestroyInstance(_instance, _allocator); + _instance = nullptr; + } +} + +void VulkanContext::initDevice(VkSurfaceKHR surface) { + _pickPhysicalDevice(surface); + _createLogicalDevice(); + + volkLoadDevice(_device); +} + +namespace details { + +static u32 debugCallback( + VkDebugUtilsMessageSeverityFlagBitsEXT messageSeverity, + VkDebugUtilsMessageTypeFlagsEXT messageType, + const VkDebugUtilsMessengerCallbackDataEXT* pCallbackData, + void* pUserData +) { + LogLevel level = LogLevel::debug; + + if (messageSeverity & VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT) { + level = LogLevel::error; + } else if (messageSeverity & VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT) { + level = LogLevel::warn; + } else if (messageSeverity & VK_DEBUG_UTILS_MESSAGE_SEVERITY_INFO_BIT_EXT) { + level = LogLevel::info; + } else if (messageSeverity & VK_DEBUG_UTILS_MESSAGE_SEVERITY_VERBOSE_BIT_EXT) { + level = LogLevel::trace; + } + + Logger::log(level, "[Vulkan] {}", pCallbackData->pMessage); + return VK_FALSE; +} + +} // namespace details + +const VulkanContext::Queue& VulkanContext::getQueue(QueueType type) const { + switch (type) { + case QueueType::graphics: return _queues.graphics; + case QueueType::presentaion: return _queues.presentation; + case QueueType::compute: return _queues.compute; + case QueueType::transfer: return _queues.transfer; + default: throw exceptions::VulkanException("Invalid queue type."); + } +} + +i32 VulkanContext::findMemoryType(u32 typeFilter, VkMemoryPropertyFlags properties) const { + VkPhysicalDeviceMemoryProperties memProperties; + vkGetPhysicalDeviceMemoryProperties(_physicalDevice, &memProperties); + + for (uint32_t i = 0; i < memProperties.memoryTypeCount; i++) { + if ((typeFilter & (1 << i)) && (memProperties.memoryTypes[i].propertyFlags & properties) == properties) { + return i; + } + } + + return -1; +} + +void VulkanContext::_createInstance() { + VkApplicationInfo appInfo{ + .sType = VK_STRUCTURE_TYPE_APPLICATION_INFO, + .pApplicationName = "Hello Triangle", + .applicationVersion = VK_MAKE_VERSION(1, 0, 0), + .pEngineName = "Archimedes", + .engineVersion = VK_MAKE_VERSION(1, 0, 0), + .apiVersion = VK_API_VERSION_1_3, + }; + + std::vector layers = _getValidationLayers(); + std::vector extensions = _getRequiredExtensions(); + + VkInstanceCreateInfo createInfo{ + .sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO, + .pApplicationInfo = &appInfo, + .enabledLayerCount = (u32)layers.size(), + .ppEnabledLayerNames = layers.data(), + .enabledExtensionCount = (u32)extensions.size(), + .ppEnabledExtensionNames = extensions.data(), + }; + + VulkanUtils::vkAssert(vkCreateInstance(&createInfo, _allocator, &_instance), "Failed to create Vulkan instance."); +} + +void VulkanContext::_setupDebugMessage() { + if (vkCreateDebugUtilsMessengerEXT == nullptr) { + Logger::warn("vkCreateDebugUtilsMessengerEXT is not available, debug messages will not be displayed."); + return; + } + + VkDebugUtilsMessengerCreateInfoEXT createInfo{ + .sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_MESSENGER_CREATE_INFO_EXT, + .messageSeverity = VK_DEBUG_UTILS_MESSAGE_SEVERITY_VERBOSE_BIT_EXT | + VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT, + .messageType = VK_DEBUG_UTILS_MESSAGE_TYPE_GENERAL_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_TYPE_VALIDATION_BIT_EXT | + VK_DEBUG_UTILS_MESSAGE_TYPE_PERFORMANCE_BIT_EXT, + .pfnUserCallback = details::debugCallback, + .pUserData = nullptr, + }; + + Logger::info("[Vulkan] Setting up debug messenger"); + VulkanUtils::vkAssert( + vkCreateDebugUtilsMessengerEXT(_instance, &createInfo, _allocator, &_debugMessenger), + "Failed to setup debug messenger." + ); +} + +void VulkanContext::_pickPhysicalDevice(VkSurfaceKHR surface) { + u32 deviceCount = 0; + vkEnumeratePhysicalDevices(_instance, &deviceCount, nullptr); + + if (deviceCount == 0) { + throw exceptions::VulkanException("Failed to find GPUs with Vulkan support!"); + } + + std::vector devices(deviceCount); + vkEnumeratePhysicalDevices(_instance, &deviceCount, devices.data()); + + i32 bestScore = -1; + for (const auto& device : devices) { + if (i32 score = _getDeviceScore(device, surface); score >= 0 && bestScore < score) { + _physicalDevice = device; + bestScore = score; + } + } + + if (_physicalDevice == VK_NULL_HANDLE) { + throw exceptions::VulkanException("Failed to find a suitable GPU!"); + } + + _queues = _getDeviceQueues(_physicalDevice, surface); +} + +void VulkanContext::_createLogicalDevice() { + // Graphics Queue + std::set queueFamilies = { _queues.graphics.index, + _queues.presentation.index, + _queues.transfer.index, + _queues.compute.index }; + + f32 queuePriority = 1.f; + std::vector queueCreateInfos; + + for (auto&& queueFamily : queueFamilies) { + queueCreateInfos.push_back( + { .sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO, + .queueFamilyIndex = queueFamily, + .queueCount = 1, + .pQueuePriorities = &queuePriority } + ); + } + + VkPhysicalDeviceVulkan12Features vulkan12features = { + .sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_2_FEATURES, + .descriptorIndexing = true, + .shaderSampledImageArrayNonUniformIndexing = true, + .descriptorBindingPartiallyBound = true, + .descriptorBindingVariableDescriptorCount = true, + .runtimeDescriptorArray = true, + .timelineSemaphore = true, + .bufferDeviceAddress = true, + }; + VkPhysicalDeviceVulkan13Features vulkan13features = { + .sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_3_FEATURES, + .pNext = &vulkan12features, + .synchronization2 = true, + }; + + VkPhysicalDeviceFeatures deviceFeatures = { + .imageCubeArray = true, + .geometryShader = true, + .tessellationShader = true, + .dualSrcBlend = true, + .fillModeNonSolid = true, + .samplerAnisotropy = true, + .textureCompressionBC = true, + .fragmentStoresAndAtomics = true, + .shaderImageGatherExtended = true, + .shaderInt16 = true, + }; + + VkDeviceCreateInfo createInfo = { + .sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO, + .pNext = &vulkan13features, + .queueCreateInfoCount = (u32)queueCreateInfos.size(), + .pQueueCreateInfos = queueCreateInfos.data(), + + .enabledExtensionCount = (u32)DEVICE_EXTENSIONS.size(), + .ppEnabledExtensionNames = DEVICE_EXTENSIONS.data(), + + .pEnabledFeatures = &deviceFeatures, + }; + + // Backward compatibility + { + if (_areValidationLayersEnabled()) { + createInfo.enabledLayerCount = (u32)VALIDATION_LAYERS.size(); + createInfo.ppEnabledLayerNames = VALIDATION_LAYERS.data(); + } else { + createInfo.enabledLayerCount = 0; + } + } + + VulkanUtils::vkAssert( + vkCreateDevice(_physicalDevice, &createInfo, _allocator, &_device), + "Failed to create logical device!" + ); + + vkGetDeviceQueue(_device, _queues.graphics.index, 0, &_queues.graphics.queue); + vkGetDeviceQueue(_device, _queues.presentation.index, 0, &_queues.presentation.queue); + vkGetDeviceQueue(_device, _queues.transfer.index, 0, &_queues.transfer.queue); + vkGetDeviceQueue(_device, _queues.compute.index, 0, &_queues.compute.queue); +} + +bool VulkanContext::_areValidationLayersEnabled() const { + return _enableValidationLayers; +} + +std::vector VulkanContext::_getValidationLayers() { + if (!_areValidationLayersEnabled()) { + return {}; + } + + std::vector layers{ VALIDATION_LAYERS.begin(), VALIDATION_LAYERS.end() }; + + // Get all avalible layers + uint32_t layerCount; + vkEnumerateInstanceLayerProperties(&layerCount, nullptr); + std::vector availableLayers(layerCount); + vkEnumerateInstanceLayerProperties(&layerCount, availableLayers.data()); + + // Filter out missing layers + std::erase_if(layers, [&](auto&& layer) { + for (auto&& l : availableLayers) { + if (std::strcmp(layer, l.layerName) == 0) { + return false; + } + } + return true; + }); + + Logger::trace("Available layers {}:", availableLayers.size()); + for (auto&& layer : availableLayers) { + Logger::trace(" - {}", layer.layerName); + } + + return layers; +} + +std::vector VulkanContext::_getRequiredExtensions() const { + uint32_t glfwExtensionCount = 0; + const char** glfwExtensions = glfwGetRequiredInstanceExtensions(&glfwExtensionCount); + std::vector extensions(glfwExtensions, glfwExtensions + glfwExtensionCount); + + if (_areValidationLayersEnabled()) { + extensions.push_back(VK_EXT_DEBUG_UTILS_EXTENSION_NAME); + } + + return extensions; +} + +int VulkanContext::_getDeviceScore(VkPhysicalDevice device, VkSurfaceKHR surface) { + int score = 0; + VkPhysicalDeviceProperties deviceProperties; + VkPhysicalDeviceFeatures deviceFeatures; + vkGetPhysicalDeviceProperties(device, &deviceProperties); + vkGetPhysicalDeviceFeatures(device, &deviceFeatures); + + // Device features requirements + { + if (!deviceFeatures.geometryShader) { + return -1; + } + + if (deviceFeatures.samplerAnisotropy) { + score += 1; + } + } + + // Device Extension requirements + { + uint32_t extensionCount; + vkEnumerateDeviceExtensionProperties(device, nullptr, &extensionCount, nullptr); + + std::vector availableExtensions(extensionCount); + vkEnumerateDeviceExtensionProperties(device, nullptr, &extensionCount, availableExtensions.data()); + + std::set requiredExtensions = { DEVICE_EXTENSIONS.begin(), DEVICE_EXTENSIONS.end() }; + + for (const auto& [extensionName, specVersion] : availableExtensions) { + requiredExtensions.erase(extensionName); + } + + if (!requiredExtensions.empty()) { + return -1; + } + } + + // Device Queue Families requirements + Queues queues = _getDeviceQueues(device, surface); + if (!queues.isComplete()) { + return -1; + } + + // Required Swapchain Support + VulkanSwapchain::SupportDetails swapchainSupport = + VulkanSwapchain::SupportDetails::getSupportDetails(device, surface); + + if (swapchainSupport.formats.empty()) { + return -1; + } + + if (swapchainSupport.presentModes.empty()) { + return -1; + } + + switch (deviceProperties.deviceType) { + case VK_PHYSICAL_DEVICE_TYPE_OTHER: + case VK_PHYSICAL_DEVICE_TYPE_MAX_ENUM: score += 1; break; + + case VK_PHYSICAL_DEVICE_TYPE_CPU: + case VK_PHYSICAL_DEVICE_TYPE_VIRTUAL_GPU: + case VK_PHYSICAL_DEVICE_TYPE_INTEGRATED_GPU: score += 10; break; + + case VK_PHYSICAL_DEVICE_TYPE_DISCRETE_GPU: score += 20; break; + } + + return score; +} + +VkFormat VulkanContext::findSupportedFormat( + const std::vector& candidates, + VkImageTiling tiling, + VkFormatFeatureFlags features +) const { + for (VkFormat format : candidates) { + VkFormatProperties props; + vkGetPhysicalDeviceFormatProperties(_physicalDevice, format, &props); + + if (tiling == VK_IMAGE_TILING_LINEAR && (props.linearTilingFeatures & features) == features) { + return format; + } + + if (tiling == VK_IMAGE_TILING_OPTIMAL && (props.optimalTilingFeatures & features) == features) { + return format; + } + } + throw exceptions::VulkanException("Failed to find supported format!"); +} + +VulkanContext::Queues VulkanContext::_getDeviceQueues(VkPhysicalDevice device, VkSurfaceKHR surface) { + Queues queues; + + uint32_t queueFamilyCount = 0; + vkGetPhysicalDeviceQueueFamilyProperties(device, &queueFamilyCount, nullptr); + + std::vector queueFamilies(queueFamilyCount); + vkGetPhysicalDeviceQueueFamilyProperties(device, &queueFamilyCount, queueFamilies.data()); + + u32 i = 0; + for (const auto& queueFamily : queueFamilies) { + VkBool32 presentSupport = false; + vkGetPhysicalDeviceSurfaceSupportKHR(device, i, surface, &presentSupport); + + if (queues.graphics.index == -1) { + if (queueFamily.queueCount > 0 && (queueFamily.queueFlags & VK_QUEUE_GRAPHICS_BIT)) { + queues.graphics.index = i; + } + } + + if (queues.compute.index == -1) { + if (queueFamily.queueCount > 0 && (queueFamily.queueFlags & VK_QUEUE_COMPUTE_BIT) && + !(queueFamily.queueFlags & VK_QUEUE_GRAPHICS_BIT)) { + queues.compute.index = i; + } + } + + if (queues.transfer.index == -1) { + if (queueFamily.queueCount > 0 && (queueFamily.queueFlags & VK_QUEUE_TRANSFER_BIT) && + !(queueFamily.queueFlags & VK_QUEUE_COMPUTE_BIT) && !(queueFamily.queueFlags & VK_QUEUE_GRAPHICS_BIT)) { + queues.transfer.index = i; + } + } + + if (queues.presentation.index == -1) { + if (queueFamily.queueCount > 0 && presentSupport) { + queues.presentation.index = i; + } + } + + if (queues.isComplete()) { + break; + } + + i++; + } + + return queues; +} + +} // namespace arch::gfx::vulkan diff --git a/src/platform/vulkan/VulkanContext.h b/src/platform/vulkan/VulkanContext.h new file mode 100644 index 0000000..2a2b853 --- /dev/null +++ b/src/platform/vulkan/VulkanContext.h @@ -0,0 +1,127 @@ +#pragma once + +#include +#include + +#include "Mmath.h" +#include "VulkanSwapchain.h" +#include "exceptions/VulkanException.h" +#include + +namespace arch::gfx::vulkan { + +class VulkanContext { +public: + enum class QueueType : std::size_t { + none, + + graphics, + presentaion, + transfer, + compute, + }; + + struct Queue { + u32 index = ~0u; + QueueType type = QueueType::none; + VkQueue queue = nullptr; + + Queue(QueueType type): type(type) {} + }; + + struct Queues { + Queue graphics{ QueueType::graphics }; + Queue presentation{ QueueType::presentaion }; + Queue transfer{ QueueType::transfer }; + Queue compute{ QueueType::compute }; + + bool isComplete() const { + return graphics.index != ~0u && presentation.index != ~0u && transfer.index != ~0u && compute.index != ~0u; + } + }; + +public: + VulkanContext(bool enableValidationLayers = true); + + virtual ~VulkanContext(); + + VulkanContext(const VulkanContext&) = delete; + VulkanContext& operator=(const VulkanContext&) = delete; + + VulkanContext(VulkanContext&&) = default; + VulkanContext& operator=(VulkanContext&&) = default; + +public: + void initDevice(VkSurfaceKHR surface); + +public: + VkInstance getInstance() const { return _instance; } + + VkAllocationCallbacks* getAllocator() const { return _allocator; } + + VkPhysicalDevice getPhysicalDevice() const { return _physicalDevice; } + + VkDevice getDevice() const { return _device; } + + const Queues& getQueues() const { return _queues; } + + const Queue& getQueue(QueueType type) const; + + i32 findMemoryType(u32 typeFilter, VkMemoryPropertyFlags properties) const; + VkFormat findSupportedFormat( + const std::vector& candidates, + VkImageTiling tiling, + VkFormatFeatureFlags features + ) const; + +private: + + void _createInstance(); + void _setupDebugMessage(); + + void _pickPhysicalDevice(VkSurfaceKHR surface); + void _createLogicalDevice(); + +protected: + static constexpr std::array DEVICE_EXTENSIONS = { + VK_KHR_SWAPCHAIN_EXTENSION_NAME, + VK_KHR_MAINTENANCE_1_EXTENSION_NAME, + // VK_KHR_GET_PHYSICAL_DEVICE_PROPERTIES_2_EXTENSION_NAME, + VK_KHR_TIMELINE_SEMAPHORE_EXTENSION_NAME, + + // VK_KHR_DYNAMIC_RENDERING_EXTENSION_NAME, + VK_KHR_SYNCHRONIZATION_2_EXTENSION_NAME, + + // VK_EXT_DESCRIPTOR_INDEXING_EXTENSION_NAME, + // VK_EXT_BUFFER_DEVICE_ADDRESS_EXTENSION_NAME, + }; + + static constexpr std::array VALIDATION_LAYERS = { "VK_LAYER_KHRONOS_validation" }; + + bool _areValidationLayersEnabled() const; + + std::vector _getValidationLayers(); + + std::vector _getRequiredExtensions() const; + + static int _getDeviceScore(VkPhysicalDevice device, VkSurfaceKHR surface); + static Queues _getDeviceQueues(VkPhysicalDevice device, VkSurfaceKHR surface); + +private: + bool _enableValidationLayers = true; + + VkInstance _instance = nullptr; + VkDebugUtilsMessengerEXT _debugMessenger = nullptr; + + VkAllocationCallbacks* _allocator = nullptr; + + VkPhysicalDevice _physicalDevice = nullptr; + VkDevice _device = nullptr; + + std::set _enabledLayers; + std::set _instanceExtensions; + + Queues _queues; +}; + +} // namespace arch::gfx::vulkan diff --git a/src/platform/vulkan/VulkanRenderer.cpp b/src/platform/vulkan/VulkanRenderer.cpp new file mode 100644 index 0000000..960b542 --- /dev/null +++ b/src/platform/vulkan/VulkanRenderer.cpp @@ -0,0 +1,580 @@ +#include "VulkanRenderer.h" + +#include + +#include "Logger.h" +#include "VulkanUtils.h" +#include "Window.h" +#include "buffer/VulkanBufferManager.h" +#include "texture/VulkanTexture.h" +#include "texture/VulkanTextureManager.h" + +namespace arch::gfx::vulkan { + +constexpr std::string_view vertexShader = R"( +#version 450 + +layout(location = 0) out vec3 fragColor; + +vec2 positions[3] = vec2[]( + vec2(0.0, -0.5), + vec2(0.5, 0.5), + vec2(-0.5, 0.5) +); + +vec3 colors[3] = vec3[]( + vec3(1.0, 0.0, 0.0), + vec3(0.0, 1.0, 0.0), + vec3(0.0, 0.0, 1.0) +); + +void main() { + gl_Position = vec4(positions[gl_VertexIndex], 0.0, 1.0); + fragColor = colors[gl_VertexIndex]; +} +)"; + +constexpr std::string_view fragmentShader = R"( +#version 450 + +layout(location = 0) in vec3 fragColor; + +layout(location = 0) out vec4 outColor; + +void main() { + outColor = vec4(fragColor, 1.0); +} +)"; + +VkShaderModule vertModule; +VkShaderModule fragModule; + +VkPipeline pipeline; +VkPipelineLayout pipelineLayout; +VkRenderPass renderPass; + +VkCommandPool commandPool; + +VkShaderModule createShaderModule(VkDevice device, const shaderc::SpvCompilationResult& code) { + VkShaderModuleCreateInfo createInfo = { + .sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO, + .codeSize = (u64)std::distance(code.begin(), code.end()) * sizeof(u32), + .pCode = code.cbegin(), + }; + + VkShaderModule shaderModule; + VulkanUtils::vkAssert( + vkCreateShaderModule(device, &createInfo, nullptr, &shaderModule), + "Failed to create shader module." + ); + + return shaderModule; +} + +void VulkanRenderer::init(const Ref& window) { + _window = window; + + Ref renderer = std::static_pointer_cast(shared_from_this()); + + _context = createRef(); + + _swapchain = createRef(_context, window); + + _context->initDevice(_swapchain->getSurface()); + + _swapchain->updateSwapchain(); + + _bufferManager = createRef(_context); + _textureManager = createRef(_context); + + _createDepthTexture(); + + /* Render Pass */ { + // std::vector attachments{ + // { + // .format = swapchain->getFormat(), + // .samples = VK_SAMPLE_COUNT_1_BIT, + // .loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR, + // .storeOp = VK_ATTACHMENT_STORE_OP_STORE, + // .stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE, + // .stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE, + // .initialLayout = VK_IMAGE_LAYOUT_UNDEFINED, + // .finalLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR, + // }, + // + // { + // .format = VulkanUtils::getFormat(depthTexture->getFormat()), + // .samples = VK_SAMPLE_COUNT_1_BIT, + // .loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR, + // .storeOp = VK_ATTACHMENT_STORE_OP_DONT_CARE, + // .stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE, + // .stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE, + // .initialLayout = VK_IMAGE_LAYOUT_UNDEFINED, + // .finalLayout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL, + // } + // }; + // + // VkAttachmentReference colorAttachmentRef = { .attachment = 0, + // .layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL }; + // VkAttachmentReference depthAttachmentRef = { .attachment = 1, + // .layout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL }; + // + // VkSubpassDescription subpass = { + // .pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS, + // .colorAttachmentCount = 1, + // .pColorAttachments = &colorAttachmentRef, + // .pDepthStencilAttachment = &depthAttachmentRef, + // }; + // + // std::vector dependencies{ + // { + // .srcSubpass = VK_SUBPASS_EXTERNAL, + // .dstSubpass = 0, + // .srcStageMask = VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT | VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT, + // .dstStageMask = VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT | VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT, + // .srcAccessMask = VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_READ_BIT, + // .dstAccessMask = VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT | + // VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_READ_BIT,.dependencyFlags = 0, + // }, + // + // { + // .srcSubpass = 0, + // .dstSubpass = VK_SUBPASS_EXTERNAL, + // .srcStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT, + // .dstStageMask = VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT, + // .srcAccessMask = 0, + // .dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT | VK_ACCESS_COLOR_ATTACHMENT_READ_BIT, + // .dependencyFlags = 0, + // } + // }; + // + // VkRenderPassCreateInfo createInfo = { + // .sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO, + // .attachmentCount = (u32)attachments.size(), + // .pAttachments = attachments.data(), + // .subpassCount = 1, + // .pSubpasses = &subpass, + // .dependencyCount = (u32)dependencies.size(), + // .pDependencies = dependencies.data(), + // + // }; + // VulkanUtils::vkAssert( + // vkCreateRenderPass(context->getDevice(), &createInfo, context->getAllocator(), &renderPass), + // "Failed to create render pass." + // ); + + VkAttachmentDescription colorAttachment{}; + colorAttachment.format = _swapchain->getFormat(); + colorAttachment.samples = VK_SAMPLE_COUNT_1_BIT; + colorAttachment.loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR; + colorAttachment.storeOp = VK_ATTACHMENT_STORE_OP_STORE; + colorAttachment.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE; + colorAttachment.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE; + colorAttachment.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; + colorAttachment.finalLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR; + + VkAttachmentReference colorAttachmentRef{}; + colorAttachmentRef.attachment = 0; + colorAttachmentRef.layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL; + + VkSubpassDescription subpass{}; + subpass.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS; + subpass.colorAttachmentCount = 1; + subpass.pColorAttachments = &colorAttachmentRef; + + VkSubpassDependency dependency{}; + dependency.srcSubpass = VK_SUBPASS_EXTERNAL; + dependency.dstSubpass = 0; + dependency.srcStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT; + dependency.srcAccessMask = 0; + dependency.dstStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT; + dependency.dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT; + + VkRenderPassCreateInfo renderPassInfo{}; + renderPassInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO; + renderPassInfo.dependencyCount = 1; + renderPassInfo.pDependencies = &dependency; + renderPassInfo.attachmentCount = 1; + renderPassInfo.pAttachments = &colorAttachment; + renderPassInfo.subpassCount = 1; + renderPassInfo.pSubpasses = &subpass; + + if (vkCreateRenderPass(_context->getDevice(), &renderPassInfo, nullptr, &renderPass) != VK_SUCCESS) { + throw std::runtime_error("failed to create render pass!"); + } + } + + /* Pipeline (Shader/Material) */ { + shaderc::CompileOptions options; + shaderc::Compiler compiler; + vertModule = createShaderModule( + _context->getDevice(), + compiler.CompileGlslToSpv( + vertexShader.data(), + vertexShader.size(), + shaderc_glsl_vertex_shader, + "shader.vert.spv", + options + ) + ); + fragModule = createShaderModule( + _context->getDevice(), + compiler.CompileGlslToSpv( + fragmentShader.data(), + fragmentShader.size(), + shaderc_glsl_fragment_shader, + "shader.frag.spv", + options + ) + ); + + VkPipelineShaderStageCreateInfo vertShaderStageInfo{}; + vertShaderStageInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO; + vertShaderStageInfo.stage = VK_SHADER_STAGE_VERTEX_BIT; + vertShaderStageInfo.module = vertModule; + vertShaderStageInfo.pName = "main"; + + VkPipelineShaderStageCreateInfo fragShaderStageInfo{}; + fragShaderStageInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO; + fragShaderStageInfo.stage = VK_SHADER_STAGE_FRAGMENT_BIT; + fragShaderStageInfo.module = fragModule; + fragShaderStageInfo.pName = "main"; + + VkPipelineShaderStageCreateInfo shaderStages[] = { vertShaderStageInfo, fragShaderStageInfo }; + + std::vector dynamicStates = { VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR }; + + VkPipelineDynamicStateCreateInfo dynamicState{}; + dynamicState.sType = VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO; + dynamicState.dynamicStateCount = static_cast(dynamicStates.size()); + dynamicState.pDynamicStates = dynamicStates.data(); + + VkPipelineVertexInputStateCreateInfo vertexInputInfo{}; + vertexInputInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO; + vertexInputInfo.vertexBindingDescriptionCount = 0; + vertexInputInfo.pVertexBindingDescriptions = nullptr; // Optional + vertexInputInfo.vertexAttributeDescriptionCount = 0; + vertexInputInfo.pVertexAttributeDescriptions = nullptr; // Optional + + VkPipelineInputAssemblyStateCreateInfo inputAssembly{}; + inputAssembly.sType = VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO; + inputAssembly.topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST; + inputAssembly.primitiveRestartEnable = VK_FALSE; + + VkExtent2D swapChainExtent = _swapchain->getExtent(); + + VkViewport viewport{}; + viewport.x = 0.0f; + viewport.y = 0.0f; + viewport.width = (float)swapChainExtent.width; + viewport.height = (float)swapChainExtent.height; + viewport.minDepth = 0.0f; + viewport.maxDepth = 1.0f; + + VkRect2D scissor{}; + scissor.offset = { 0, 0 }; + scissor.extent = swapChainExtent; + + VkPipelineViewportStateCreateInfo viewportState{}; + viewportState.sType = VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO; + viewportState.viewportCount = 1; + viewportState.pViewports = &viewport; + viewportState.scissorCount = 1; + viewportState.pScissors = &scissor; + + VkPipelineRasterizationStateCreateInfo rasterizer{}; + rasterizer.sType = VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO; + rasterizer.depthClampEnable = VK_FALSE; + rasterizer.rasterizerDiscardEnable = VK_FALSE; + rasterizer.polygonMode = VK_POLYGON_MODE_FILL; + rasterizer.lineWidth = 1.0f; + rasterizer.cullMode = VK_CULL_MODE_BACK_BIT; + rasterizer.frontFace = VK_FRONT_FACE_CLOCKWISE; + rasterizer.depthBiasEnable = VK_FALSE; + rasterizer.depthBiasConstantFactor = 0.0f; // Optional + rasterizer.depthBiasClamp = 0.0f; // Optional + rasterizer.depthBiasSlopeFactor = 0.0f; // Optional + + VkPipelineMultisampleStateCreateInfo multisampling{}; + multisampling.sType = VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO; + multisampling.sampleShadingEnable = VK_FALSE; + multisampling.rasterizationSamples = VK_SAMPLE_COUNT_1_BIT; + multisampling.minSampleShading = 1.0f; // Optional + multisampling.pSampleMask = nullptr; // Optional + multisampling.alphaToCoverageEnable = VK_FALSE; // Optional + multisampling.alphaToOneEnable = VK_FALSE; // Optional + + VkPipelineColorBlendAttachmentState colorBlendAttachment{}; + colorBlendAttachment.colorWriteMask = VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_G_BIT | + VK_COLOR_COMPONENT_B_BIT | VK_COLOR_COMPONENT_A_BIT; + colorBlendAttachment.blendEnable = VK_TRUE; + colorBlendAttachment.srcColorBlendFactor = VK_BLEND_FACTOR_SRC_ALPHA; + colorBlendAttachment.dstColorBlendFactor = VK_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA; + colorBlendAttachment.colorBlendOp = VK_BLEND_OP_ADD; + colorBlendAttachment.srcAlphaBlendFactor = VK_BLEND_FACTOR_ONE; + colorBlendAttachment.dstAlphaBlendFactor = VK_BLEND_FACTOR_ZERO; + colorBlendAttachment.alphaBlendOp = VK_BLEND_OP_ADD; + + VkPipelineColorBlendStateCreateInfo colorBlending{}; + colorBlending.sType = VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO; + colorBlending.logicOpEnable = VK_FALSE; + colorBlending.logicOp = VK_LOGIC_OP_COPY; // Optional + colorBlending.attachmentCount = 1; + colorBlending.pAttachments = &colorBlendAttachment; + colorBlending.blendConstants[0] = 0.0f; // Optional + colorBlending.blendConstants[1] = 0.0f; // Optional + colorBlending.blendConstants[2] = 0.0f; // Optional + colorBlending.blendConstants[3] = 0.0f; // Optional + + VkPipelineLayoutCreateInfo pipelineLayoutInfo{}; + pipelineLayoutInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO; + pipelineLayoutInfo.setLayoutCount = 0; // Optional + pipelineLayoutInfo.pSetLayouts = nullptr; // Optional + pipelineLayoutInfo.pushConstantRangeCount = 0; // Optional + pipelineLayoutInfo.pPushConstantRanges = nullptr; // Optional + + if (vkCreatePipelineLayout(_context->getDevice(), &pipelineLayoutInfo, nullptr, &pipelineLayout) != + VK_SUCCESS) { + throw std::runtime_error("failed to create pipeline layout!"); + } + + VkGraphicsPipelineCreateInfo pipelineInfo{}; + pipelineInfo.sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO; + pipelineInfo.stageCount = 2; + pipelineInfo.pStages = shaderStages; + pipelineInfo.pVertexInputState = &vertexInputInfo; + pipelineInfo.pInputAssemblyState = &inputAssembly; + pipelineInfo.pViewportState = &viewportState; + pipelineInfo.pRasterizationState = &rasterizer; + pipelineInfo.pMultisampleState = &multisampling; + pipelineInfo.pDepthStencilState = nullptr; // Optional + pipelineInfo.pColorBlendState = &colorBlending; + pipelineInfo.pDynamicState = &dynamicState; + pipelineInfo.layout = pipelineLayout; + pipelineInfo.renderPass = renderPass; + pipelineInfo.subpass = 0; + pipelineInfo.basePipelineHandle = VK_NULL_HANDLE; // Optional + pipelineInfo.basePipelineIndex = -1; // Optional + + if (vkCreateGraphicsPipelines(_context->getDevice(), VK_NULL_HANDLE, 1, &pipelineInfo, nullptr, &pipeline) != + VK_SUCCESS) { + throw std::runtime_error("failed to create graphics pipeline!"); + } + } + + _createFrames(); + + /* Command Buffers */ { + VkCommandPoolCreateInfo poolInfo{}; + poolInfo.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO; + poolInfo.flags = VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT; + poolInfo.queueFamilyIndex = _context->getQueue(VulkanContext::QueueType::graphics).index; + + if (vkCreateCommandPool(_context->getDevice(), &poolInfo, nullptr, &commandPool) != VK_SUCCESS) { + throw std::runtime_error("failed to create command pool!"); + } + + VkCommandBufferAllocateInfo allocInfo{}; + allocInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO; + allocInfo.commandPool = commandPool; + allocInfo.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY; + allocInfo.commandBufferCount = 1; + + for (auto& frame : _frames) { + if (vkAllocateCommandBuffers(_context->getDevice(), &allocInfo, &frame.commandBuffer) != VK_SUCCESS) { + throw std::runtime_error("failed to allocate command buffers!"); + } + } + } + + Logger::info("Created Vulkan instance."); +} + +void VulkanRenderer::shutdown() { + if (_swapchain.use_count() > 1) { + Logger::warn("Swapchain is still in use."); + } + _swapchain.reset(); + + _depthTexture.reset(); + + _bufferManager.reset(); + _textureManager.reset(); + + if (_context.use_count() > 1) { + Logger::warn("Context is still in use."); + } + _context.reset(); +} + +Ref VulkanRenderer::getBufferManager() { + return _bufferManager; +} + +Ref VulkanRenderer::getTextureManager() { + return _textureManager; +} + +void VulkanRenderer::render(const Ref& mesh, const Mat4x4& transform) {} + +void VulkanRenderer::_createDepthTexture() { + // Depthbuffer + VkFormat format = _context->findSupportedFormat( + { VK_FORMAT_D32_SFLOAT, VK_FORMAT_D32_SFLOAT_S8_UINT, VK_FORMAT_D24_UNORM_S8_UINT }, + VK_IMAGE_TILING_OPTIMAL, + VK_FORMAT_FEATURE_DEPTH_STENCIL_ATTACHMENT_BIT + ); + + VkExtent2D extent = _swapchain->getExtent(); + + _depthTexture = std::static_pointer_cast( + _textureManager->createTexture2D(extent.width, extent.height, nullptr, VulkanUtils::getFormat(format)) + ); +} + +void VulkanRenderer::_createFrames() { + VkExtent2D extent = _swapchain->getExtent(); + + _frames.resize(_swapchain->getFrameCount()); + for (int i = 0; i < _frames.size(); ++i) { + const VulkanSwapchain::Frame& frame = _swapchain->getFrame(i); + std::array attachments = { frame.imageView /*, depthTexture->getImage().getImageView()*/ }; + + VkFramebufferCreateInfo framebufferCraeteInfo = { + .sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO, + .renderPass = renderPass, + .attachmentCount = attachments.size(), + .pAttachments = attachments.data(), + .width = extent.width, + .height = extent.height, + .layers = 1, + }; + + vkCreateFramebuffer(_context->getDevice(), &framebufferCraeteInfo, nullptr, &_frames[i].framebuffer); + + VkSemaphoreCreateInfo semaphoreInfo{}; + semaphoreInfo.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO; + VkFenceCreateInfo fenceInfo{}; + fenceInfo.sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO; + fenceInfo.flags = VK_FENCE_CREATE_SIGNALED_BIT; + + if (vkCreateSemaphore(_context->getDevice(), &semaphoreInfo, nullptr, &_frames[i].imageAvailableSemaphore) != + VK_SUCCESS || + vkCreateSemaphore(_context->getDevice(), &semaphoreInfo, nullptr, &_frames[i].renderFinishedSemaphore) != + VK_SUCCESS || + vkCreateFence(_context->getDevice(), &fenceInfo, nullptr, &_frames[i].inFlightFence) != VK_SUCCESS) { + throw std::runtime_error("failed to create semaphores!"); + } + } +} // namespace arch::gfx::vulkan + +void VulkanRenderer::onResize(u32 width, u32 height) {} + +u32 imageIndex; + +void VulkanRenderer::beginFrame() { + auto& frame = _frames[_frameIndex]; + + vkWaitForFences(_context->getDevice(), 1, &frame.inFlightFence, VK_TRUE, UINT64_MAX); + vkResetFences(_context->getDevice(), 1, &frame.inFlightFence); + + vkAcquireNextImageKHR( + _context->getDevice(), + _swapchain->getSwapchain(), + UINT64_MAX, + frame.imageAvailableSemaphore, + VK_NULL_HANDLE, + &imageIndex + ); + + vkResetCommandBuffer(frame.commandBuffer, 0); + + VkCommandBufferBeginInfo beginInfo{}; + beginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO; + beginInfo.flags = 0; // Optional + beginInfo.pInheritanceInfo = nullptr; // Optional + + if (vkBeginCommandBuffer(frame.commandBuffer, &beginInfo) != VK_SUCCESS) { + throw std::runtime_error("failed to begin recording command buffer!"); + } + + VkRenderPassBeginInfo renderPassInfo{}; + renderPassInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO; + renderPassInfo.renderPass = renderPass; + renderPassInfo.framebuffer = frame.framebuffer; + renderPassInfo.renderArea.offset = { 0, 0 }; + renderPassInfo.renderArea.extent = _swapchain->getExtent(); + VkClearValue clearColor = { { { 0.0f, 0.0f, 0.0f, 1.0f } } }; + renderPassInfo.clearValueCount = 1; + renderPassInfo.pClearValues = &clearColor; + vkCmdBeginRenderPass(frame.commandBuffer, &renderPassInfo, VK_SUBPASS_CONTENTS_INLINE); + + vkCmdBindPipeline(frame.commandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline); + + VkViewport viewport{}; + viewport.x = 0.0f; + viewport.y = 0.0f; + viewport.width = static_cast(_swapchain->getExtent().width); + viewport.height = static_cast(_swapchain->getExtent().height); + viewport.minDepth = 0.0f; + viewport.maxDepth = 1.0f; + vkCmdSetViewport(frame.commandBuffer, 0, 1, &viewport); + + VkRect2D scissor{}; + scissor.offset = { 0, 0 }; + scissor.extent = _swapchain->getExtent(); + vkCmdSetScissor(frame.commandBuffer, 0, 1, &scissor); + + vkCmdDraw(frame.commandBuffer, 3, 1, 0, 0); +} + +void VulkanRenderer::present() { + auto& frame = _frames[_frameIndex]; + + vkCmdEndRenderPass(frame.commandBuffer); + + if (vkEndCommandBuffer(frame.commandBuffer) != VK_SUCCESS) { + throw std::runtime_error("failed to record command buffer!"); + } + + VkSubmitInfo submitInfo{}; + submitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO; + VkSemaphore waitSemaphores[] = { frame.imageAvailableSemaphore }; + VkPipelineStageFlags waitStages[] = { VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT }; + submitInfo.waitSemaphoreCount = 1; + submitInfo.pWaitSemaphores = waitSemaphores; + submitInfo.pWaitDstStageMask = waitStages; + submitInfo.commandBufferCount = 1; + submitInfo.pCommandBuffers = &frame.commandBuffer; + + VkSemaphore signalSemaphores[] = { frame.renderFinishedSemaphore }; + submitInfo.signalSemaphoreCount = 1; + submitInfo.pSignalSemaphores = signalSemaphores; + + if (vkQueueSubmit( + _context->getQueue(VulkanContext::QueueType::graphics).queue, + 1, + &submitInfo, + frame.inFlightFence + ) != VK_SUCCESS) { + throw std::runtime_error("failed to submit draw command buffer!"); + } + + VkPresentInfoKHR presentInfo{}; + presentInfo.sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR; + + presentInfo.waitSemaphoreCount = 1; + presentInfo.pWaitSemaphores = signalSemaphores; + + VkSwapchainKHR swapChains[] = { _swapchain->getSwapchain() }; + presentInfo.swapchainCount = 1; + presentInfo.pSwapchains = swapChains; + presentInfo.pImageIndices = &imageIndex; + presentInfo.pResults = nullptr; // Optional + + vkQueuePresentKHR(_context->getQueue(VulkanContext::QueueType::presentaion).queue, &presentInfo); + + _frameIndex = (_frameIndex + 1) % _frames.size(); +} + +} // namespace arch::gfx::vulkan diff --git a/src/platform/vulkan/VulkanRenderer.h b/src/platform/vulkan/VulkanRenderer.h new file mode 100644 index 0000000..08bd235 --- /dev/null +++ b/src/platform/vulkan/VulkanRenderer.h @@ -0,0 +1,65 @@ +#pragma once + +#include "VulkanContext.h" +#include "VulkanSwapchain.h" +#include "Window.h" +#include "gfx/Renderer.h" +#include "texture/VulkanTexture.h" + +namespace arch::gfx::vulkan { + +namespace buffer { +class VulkanBufferManager; +} + +namespace texture { +class VulkanTextureManager; +} + +class VulkanRenderer final: public Renderer { +public: + VulkanRenderer(): Renderer(RenderingAPI::vulkan) {} + + ~VulkanRenderer() override = default; + + void init(const Ref& window) override; + void shutdown() override; + + Ref getBufferManager() override; + + Ref getTextureManager() override; + + void render(const Ref& mesh, const Mat4x4& transform) override; + +private: + void _createDepthTexture(); + void _createFrames(); + +public: + void onResize(u32 width, u32 height) override; + + void beginFrame() override; + void present() override; + +private: + Ref _bufferManager; + Ref _textureManager; + + Ref _context; + Ref _swapchain; + + struct FrameData { + VkFramebuffer framebuffer; + + VkCommandBuffer commandBuffer; + VkSemaphore imageAvailableSemaphore; + VkSemaphore renderFinishedSemaphore; + VkFence inFlightFence; + }; + + std::vector _frames; + std::uint32_t _frameIndex = 0; + Ref _depthTexture; +}; + +} // namespace arch::gfx::vulkan diff --git a/src/platform/vulkan/VulkanSwapchain.cpp b/src/platform/vulkan/VulkanSwapchain.cpp new file mode 100644 index 0000000..ff924f4 --- /dev/null +++ b/src/platform/vulkan/VulkanSwapchain.cpp @@ -0,0 +1,206 @@ +#include "VulkanSwapchain.h" + +#include + +#include "VulkanContext.h" +#include "VulkanUtils.h" +#include "Window.h" + +namespace arch::gfx::vulkan { + +VulkanSwapchain::VulkanSwapchain(const Ref& context, const Ref& window): + _context(context), + _window(window) { + _createSurface(); +} + +VulkanSwapchain::~VulkanSwapchain() { + _cleanupSwapchain(); + + vkDestroySurfaceKHR(_context->getInstance(), _surface, _context->getAllocator()); + _surface = nullptr; +} + +void VulkanSwapchain::updateSwapchain() { + if (_swapchain == nullptr) { + _supportDetails = SupportDetails::getSupportDetails(_context->getPhysicalDevice(), _surface); + + _createSwapchain(); + } else { + _recreateSwapchain(); + } +} + +void VulkanSwapchain::_createSurface() { + VulkanUtils::vkAssert( + glfwCreateWindowSurface(_context->getInstance(), _window->get(), _context->getAllocator(), &_surface), + "Failed to create window surface." + ); +} + +void VulkanSwapchain::_createSwapchain() { + VkSurfaceFormatKHR surfaceFormat = _supportDetails.formats[0]; + for (const auto& availableFormat : _supportDetails.formats) { + if (availableFormat.format == VK_FORMAT_B8G8R8A8_SRGB && + availableFormat.colorSpace == VK_COLOR_SPACE_SRGB_NONLINEAR_KHR) { + surfaceFormat = availableFormat; + break; + } + } + + VkPresentModeKHR presentMode = VK_PRESENT_MODE_FIFO_KHR; +#if false // Triple buffering - currently not supported + for (const auto& availablePresentMode : swapchainSupportDetails.presentModes) { + if (availablePresentMode == VK_PRESENT_MODE_MAILBOX_KHR) { + presentMode = availablePresentMode; + break; + } + } +#endif + + _format = surfaceFormat.format; + + if (_supportDetails.capabilities.currentExtent.width != std::numeric_limits::max()) { + _extent = _supportDetails.capabilities.currentExtent; + } else { + i32 width, height; + glfwGetFramebufferSize(_window->get(), &width, &height); + + _extent = { std::clamp( + (u32)width, + _supportDetails.capabilities.minImageExtent.width, + _supportDetails.capabilities.maxImageExtent.width + ), + std::clamp( + (u32)height, + _supportDetails.capabilities.minImageExtent.height, + _supportDetails.capabilities.maxImageExtent.height + ) }; + } + + u32 imageCount = _supportDetails.capabilities.minImageCount + 1; + if (_supportDetails.capabilities.maxImageCount > 0 && imageCount > _supportDetails.capabilities.maxImageCount) { + imageCount = _supportDetails.capabilities.maxImageCount; + } + + VkSwapchainCreateInfoKHR createInfo = { + .sType = VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR, + .surface = _surface, + .minImageCount = imageCount, + .imageFormat = surfaceFormat.format, + .imageColorSpace = surfaceFormat.colorSpace, + .imageExtent = _extent, + .imageArrayLayers = 1, + .imageUsage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT, + .preTransform = _supportDetails.capabilities.currentTransform, + .compositeAlpha = VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR, + .presentMode = presentMode, + .clipped = VK_TRUE, + }; + + std::array queueFamilyIndices = { + _context->getQueue(VulkanContext::QueueType::graphics).index, + _context->getQueue(VulkanContext::QueueType::presentaion).index, + }; + + if (queueFamilyIndices[0] != queueFamilyIndices[1]) { + createInfo.imageSharingMode = VK_SHARING_MODE_CONCURRENT; + createInfo.queueFamilyIndexCount = (u32)queueFamilyIndices.size(); + createInfo.pQueueFamilyIndices = queueFamilyIndices.data(); + } else { + createInfo.imageSharingMode = VK_SHARING_MODE_EXCLUSIVE; + createInfo.queueFamilyIndexCount = 0; // Optional + createInfo.pQueueFamilyIndices = nullptr; // Optional + } + + VkDevice device = _context->getDevice(); + VkAllocationCallbacks* allocator = _context->getAllocator(); + + VulkanUtils::vkAssert( + vkCreateSwapchainKHR(device, &createInfo, allocator, &_swapchain), + "Failed to create swap chain!" + ); + + vkGetSwapchainImagesKHR(device, _swapchain, &imageCount, nullptr); + std::vector swapchainImages(imageCount); + vkGetSwapchainImagesKHR(device, _swapchain, &imageCount, swapchainImages.data()); + _frames.resize(imageCount); + for (u64 i = 0; i < _frames.size(); ++i) { + _frames[i].image = swapchainImages[i]; + } + + // Image Views + for (u64 i = 0; i < _frames.size(); i++) { + VkImageViewCreateInfo imageViewCreateInfo = { + .sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO, + .image = _frames[i].image, + .viewType = VK_IMAGE_VIEW_TYPE_2D, + .format = _format, + .components = { + .r = VK_COMPONENT_SWIZZLE_IDENTITY, + .g = VK_COMPONENT_SWIZZLE_IDENTITY, + .b = VK_COMPONENT_SWIZZLE_IDENTITY, + .a = VK_COMPONENT_SWIZZLE_IDENTITY, + }, + .subresourceRange = { + .aspectMask = VK_IMAGE_ASPECT_COLOR_BIT, + .baseMipLevel = 0, + .levelCount = 1, + .baseArrayLayer = 0, + .layerCount = 1, + } + }; + + VulkanUtils::vkAssert( + vkCreateImageView(device, &imageViewCreateInfo, allocator, &_frames[i].imageView), + "Failed to create image view!" + ); + } +} + +void VulkanSwapchain::_cleanupSwapchain() { + VkDevice device = _context->getDevice(); + VkAllocationCallbacks* allocator = _context->getAllocator(); + + for (const auto& frame : _frames) { + vkDestroyImageView(device, frame.imageView, allocator); + } + _frames.clear(); + + vkDestroySwapchainKHR(device, _swapchain, allocator); + _swapchain = nullptr; +} + +void VulkanSwapchain::_recreateSwapchain() { + vkDeviceWaitIdle(_context->getDevice()); + + _cleanupSwapchain(); + + _supportDetails = SupportDetails::getSupportDetails(_context->getPhysicalDevice(), _surface); + + _createSwapchain(); +} + +VulkanSwapchain::SupportDetails VulkanSwapchain::SupportDetails::getSupportDetails( + VkPhysicalDevice device, + VkSurfaceKHR surface +) { + SupportDetails swapchain; + + vkGetPhysicalDeviceSurfaceCapabilitiesKHR(device, surface, &swapchain.capabilities); + + u32 formatCount; + vkGetPhysicalDeviceSurfaceFormatsKHR(device, surface, &formatCount, nullptr); + + swapchain.formats.resize(formatCount); + vkGetPhysicalDeviceSurfaceFormatsKHR(device, surface, &formatCount, swapchain.formats.data()); + + u32 presentModeCount; + vkGetPhysicalDeviceSurfacePresentModesKHR(device, surface, &presentModeCount, nullptr); + + swapchain.presentModes.resize(presentModeCount); + vkGetPhysicalDeviceSurfacePresentModesKHR(device, surface, &presentModeCount, swapchain.presentModes.data()); + return swapchain; +} + +} // namespace arch::gfx::vulkan diff --git a/src/platform/vulkan/VulkanSwapchain.h b/src/platform/vulkan/VulkanSwapchain.h new file mode 100644 index 0000000..39c1be0 --- /dev/null +++ b/src/platform/vulkan/VulkanSwapchain.h @@ -0,0 +1,80 @@ +#pragma once +#include + +#include "Mmath.h" +#include "Ref.h" +#include + +namespace arch { +class Window; +} + +namespace arch::gfx::vulkan { + +class VulkanContext; + +class VulkanSwapchain { +public: + struct SupportDetails { + VkSurfaceCapabilitiesKHR capabilities{}; + std::vector formats; + std::vector presentModes; + + static SupportDetails getSupportDetails(VkPhysicalDevice device, VkSurfaceKHR surface); + }; + + struct Frame { + VkImage image; + VkImageView imageView; + }; + +public: + VulkanSwapchain(const Ref& context, const Ref& window); + ~VulkanSwapchain(); + + VulkanSwapchain(const VulkanSwapchain&) = delete; + VulkanSwapchain& operator=(const VulkanSwapchain&) = delete; + + VulkanSwapchain(VulkanSwapchain&&) = default; + VulkanSwapchain& operator=(VulkanSwapchain&&) = default; + +public: + void updateSwapchain(); + +public: + VkSurfaceKHR getSurface() const { return _surface; } + + VkSwapchainKHR getSwapchain() const { return _swapchain; } + + VkFormat getFormat() const { return _format; } + + VkExtent2D getExtent() const { return _extent; } + + u64 getFrameCount() const { return _frames.size(); } + + const Frame& getFrame(int index) const { return _frames[index]; } + +private: + void _createSurface(); + + void _createSwapchain(); + void _cleanupSwapchain(); + void _recreateSwapchain(); + +private: + Ref _context; + Ref _window; + + VkSurfaceKHR _surface = nullptr; + VkSwapchainKHR _swapchain = nullptr; + + VkPresentModeKHR _presentMode = {}; + VkFormat _format = VK_FORMAT_UNDEFINED; + VkExtent2D _extent = { 0, 0 }; + + std::vector _frames; + + SupportDetails _supportDetails; +}; + +} // namespace arch::gfx::vulkan diff --git a/src/platform/vulkan/VulkanUtils.cpp b/src/platform/vulkan/VulkanUtils.cpp new file mode 100644 index 0000000..7a2520a --- /dev/null +++ b/src/platform/vulkan/VulkanUtils.cpp @@ -0,0 +1,154 @@ +#include "VulkanUtils.h" + +#include "Logger.h" +#include "exceptions/VulkanException.h" + +namespace arch::gfx::vulkan { + +void VulkanUtils::vkAssert(VkResult result, const std::string& message, const std::source_location& location) { + if (result != VK_SUCCESS) { + throw exceptions::VulkanException(message, location); + } +} + +VkFormat VulkanUtils::getFormat(GraphicsFormat format) { + switch (format) { + default: + case GraphicsFormat::none: return VK_FORMAT_UNDEFINED; + + case GraphicsFormat::r8: return VK_FORMAT_R8_UINT; + case GraphicsFormat::r8s: return VK_FORMAT_R8_SINT; + case GraphicsFormat::r8_norm: return VK_FORMAT_R8_UNORM; + case GraphicsFormat::r8s_norm: return VK_FORMAT_R8_SNORM; + case GraphicsFormat::r16: return VK_FORMAT_R16_UINT; + case GraphicsFormat::r16s: return VK_FORMAT_R16_SINT; + case GraphicsFormat::r16_norm: return VK_FORMAT_R16_UNORM; + case GraphicsFormat::r16s_norm: return VK_FORMAT_R16_SNORM; + case GraphicsFormat::r16f: return VK_FORMAT_R16_SFLOAT; + case GraphicsFormat::rg8: return VK_FORMAT_R8G8_UINT; + case GraphicsFormat::rg8s: return VK_FORMAT_R8G8_SINT; + case GraphicsFormat::rg8_norm: return VK_FORMAT_R8G8_UNORM; + case GraphicsFormat::rg8s_norm: return VK_FORMAT_R8G8_SNORM; + case GraphicsFormat::bgra4_norm: return VK_FORMAT_B4G4R4A4_UNORM_PACK16; + case GraphicsFormat::b5g6r5_norm: return VK_FORMAT_B5G6R5_UNORM_PACK16; + case GraphicsFormat::b5g5r5a1_norm: return VK_FORMAT_B5G5R5A1_UNORM_PACK16; + case GraphicsFormat::rgba8: return VK_FORMAT_R8G8B8A8_UINT; + case GraphicsFormat::rgba8s: return VK_FORMAT_R8G8B8A8_SINT; + case GraphicsFormat::rgba8_norm: return VK_FORMAT_R8G8B8A8_UNORM; + case GraphicsFormat::rgba8s_norm: return VK_FORMAT_R8G8B8A8_SNORM; + case GraphicsFormat::bgra8_norm: return VK_FORMAT_B8G8R8A8_UNORM; + case GraphicsFormat::srgba8_norm: return VK_FORMAT_R8G8B8A8_SRGB; + case GraphicsFormat::sbgra8_norm: return VK_FORMAT_B8G8R8A8_SRGB; + case GraphicsFormat::r10g10b10a2_norm: return VK_FORMAT_A2B10G10R10_UNORM_PACK32; + case GraphicsFormat::r11g11b10f: return VK_FORMAT_B10G11R11_UFLOAT_PACK32; + case GraphicsFormat::rg16: return VK_FORMAT_R16G16_UINT; + case GraphicsFormat::rg16s: return VK_FORMAT_R16G16_SINT; + case GraphicsFormat::rg16_norm: return VK_FORMAT_R16G16_UNORM; + case GraphicsFormat::rg16s_norm: return VK_FORMAT_R16G16_SNORM; + case GraphicsFormat::rg16f: return VK_FORMAT_R16G16_SFLOAT; + case GraphicsFormat::r32: return VK_FORMAT_R32_UINT; + case GraphicsFormat::r32s: return VK_FORMAT_R32_SINT; + case GraphicsFormat::r32f: return VK_FORMAT_R32_SFLOAT; + case GraphicsFormat::rgba16: return VK_FORMAT_R16G16B16A16_UINT; + case GraphicsFormat::rgba16s: return VK_FORMAT_R16G16B16A16_SINT; + case GraphicsFormat::rgba16f: return VK_FORMAT_R16G16B16A16_SFLOAT; + case GraphicsFormat::rgba16_norm: return VK_FORMAT_R16G16B16A16_UNORM; + case GraphicsFormat::rgba16s_norm: return VK_FORMAT_R16G16B16A16_SNORM; + case GraphicsFormat::rg32: return VK_FORMAT_R32G32_UINT; + case GraphicsFormat::rg32s: return VK_FORMAT_R32G32_SINT; + case GraphicsFormat::rg32f: return VK_FORMAT_R32G32_SFLOAT; + case GraphicsFormat::rgb32: return VK_FORMAT_R32G32B32_UINT; + case GraphicsFormat::rgb32s: return VK_FORMAT_R32G32B32_SINT; + case GraphicsFormat::rgb32f: return VK_FORMAT_R32G32B32_SFLOAT; + case GraphicsFormat::rgba32: return VK_FORMAT_R32G32B32A32_UINT; + case GraphicsFormat::rgba32s: return VK_FORMAT_R32G32B32A32_SINT; + case GraphicsFormat::rgba32f: return VK_FORMAT_R32G32B32A32_SFLOAT; + case GraphicsFormat::depth16: return VK_FORMAT_D16_UNORM; + case GraphicsFormat::depth24Stencil8: return VK_FORMAT_D24_UNORM_S8_UINT; + case GraphicsFormat::depth32f: return VK_FORMAT_D32_SFLOAT; + case GraphicsFormat::depth32fStencil8: return VK_FORMAT_D32_SFLOAT_S8_UINT; + } +} + +GraphicsFormat VulkanUtils::getFormat(VkFormat format) { + switch (format) { + default: + case VK_FORMAT_UNDEFINED: return GraphicsFormat::none; + + case VK_FORMAT_R8_UINT: return GraphicsFormat::r8; + case VK_FORMAT_R8_SINT: return GraphicsFormat::r8s; + case VK_FORMAT_R8_UNORM: return GraphicsFormat::r8_norm; + case VK_FORMAT_R8_SNORM: return GraphicsFormat::r8s_norm; + case VK_FORMAT_R16_UINT: return GraphicsFormat::r16; + case VK_FORMAT_R16_SINT: return GraphicsFormat::r16s; + case VK_FORMAT_R16_UNORM: return GraphicsFormat::r16_norm; + case VK_FORMAT_R16_SNORM: return GraphicsFormat::r16s_norm; + case VK_FORMAT_R16_SFLOAT: return GraphicsFormat::r16f; + case VK_FORMAT_R8G8_UINT: return GraphicsFormat::rg8; + case VK_FORMAT_R8G8_SINT: return GraphicsFormat::rg8s; + case VK_FORMAT_R8G8_UNORM: return GraphicsFormat::rg8_norm; + case VK_FORMAT_R8G8_SNORM: return GraphicsFormat::rg8s_norm; + case VK_FORMAT_B4G4R4A4_UNORM_PACK16: return GraphicsFormat::bgra4_norm; + case VK_FORMAT_B5G6R5_UNORM_PACK16: return GraphicsFormat::b5g6r5_norm; + case VK_FORMAT_B5G5R5A1_UNORM_PACK16: return GraphicsFormat::b5g5r5a1_norm; + case VK_FORMAT_R8G8B8A8_UINT: return GraphicsFormat::rgba8; + case VK_FORMAT_R8G8B8A8_SINT: return GraphicsFormat::rgba8s; + case VK_FORMAT_R8G8B8A8_UNORM: return GraphicsFormat::rgba8_norm; + case VK_FORMAT_R8G8B8A8_SNORM: return GraphicsFormat::rgba8s_norm; + case VK_FORMAT_B8G8R8A8_UNORM: return GraphicsFormat::bgra8_norm; + case VK_FORMAT_R8G8B8A8_SRGB: return GraphicsFormat::srgba8_norm; + case VK_FORMAT_B8G8R8A8_SRGB: return GraphicsFormat::sbgra8_norm; + case VK_FORMAT_A2B10G10R10_UNORM_PACK32: return GraphicsFormat::r10g10b10a2_norm; + case VK_FORMAT_B10G11R11_UFLOAT_PACK32: return GraphicsFormat::r11g11b10f; + case VK_FORMAT_R16G16_UINT: return GraphicsFormat::rg16; + case VK_FORMAT_R16G16_SINT: return GraphicsFormat::rg16s; + case VK_FORMAT_R16G16_UNORM: return GraphicsFormat::rg16_norm; + case VK_FORMAT_R16G16_SNORM: return GraphicsFormat::rg16s_norm; + case VK_FORMAT_R16G16_SFLOAT: return GraphicsFormat::rg16f; + case VK_FORMAT_R32_UINT: return GraphicsFormat::r32; + case VK_FORMAT_R32_SINT: return GraphicsFormat::r32s; + case VK_FORMAT_R32_SFLOAT: return GraphicsFormat::r32f; + case VK_FORMAT_R16G16B16A16_UINT: return GraphicsFormat::rgba16; + case VK_FORMAT_R16G16B16A16_SINT: return GraphicsFormat::rgba16s; + case VK_FORMAT_R16G16B16A16_SFLOAT: return GraphicsFormat::rgba16f; + case VK_FORMAT_R16G16B16A16_UNORM: return GraphicsFormat::rgba16_norm; + case VK_FORMAT_R16G16B16A16_SNORM: return GraphicsFormat::rgba16s_norm; + case VK_FORMAT_R32G32_UINT: return GraphicsFormat::rg32; + case VK_FORMAT_R32G32_SINT: return GraphicsFormat::rg32s; + case VK_FORMAT_R32G32_SFLOAT: return GraphicsFormat::rg32f; + case VK_FORMAT_R32G32B32_UINT: return GraphicsFormat::rgb32; + case VK_FORMAT_R32G32B32_SINT: return GraphicsFormat::rgb32s; + case VK_FORMAT_R32G32B32_SFLOAT: return GraphicsFormat::rgb32f; + case VK_FORMAT_R32G32B32A32_UINT: return GraphicsFormat::rgba32; + case VK_FORMAT_R32G32B32A32_SINT: return GraphicsFormat::rgba32s; + case VK_FORMAT_R32G32B32A32_SFLOAT: return GraphicsFormat::rgba32f; + case VK_FORMAT_D16_UNORM: return GraphicsFormat::depth16; + case VK_FORMAT_D24_UNORM_S8_UINT: return GraphicsFormat::depth24Stencil8; + case VK_FORMAT_D32_SFLOAT: return GraphicsFormat::depth32f; + case VK_FORMAT_D32_SFLOAT_S8_UINT: return GraphicsFormat::depth32fStencil8; + } +} + +VkImageType VulkanUtils::getType(texture::TextureType type) { + switch (type) { + case texture::TextureType::texture2D: + return VK_IMAGE_TYPE_2D; + + // case texture::TextureType::texture3D: return VK_IMAGE_TYPE_3D; + + default: return VK_IMAGE_TYPE_MAX_ENUM; + } +} + +VkImageViewType VulkanUtils::getImageViewType(texture::TextureType type) { + switch (type) { + case texture::TextureType::texture2D: + return VK_IMAGE_VIEW_TYPE_2D; + + // case texture::TextureType::texture3D: return VK_IMAGE_VIEW_TYPE_3D; + + default: return VK_IMAGE_VIEW_TYPE_MAX_ENUM; + } +} + +} // namespace arch::gfx::vulkan diff --git a/src/platform/vulkan/VulkanUtils.h b/src/platform/vulkan/VulkanUtils.h new file mode 100644 index 0000000..955579f --- /dev/null +++ b/src/platform/vulkan/VulkanUtils.h @@ -0,0 +1,26 @@ +#pragma once +#include +#include + +#include "gfx/GraphicsFormat.h" +#include "gfx/texture/TextureType.h" +#include + +namespace arch::gfx::vulkan { + +class VulkanUtils { +public: + static void vkAssert( + VkResult result, + const std::string& message, + const std::source_location& location = std::source_location::current() + ); + + static VkFormat getFormat(GraphicsFormat format); + static GraphicsFormat getFormat(VkFormat format); + + static VkImageType getType(gfx::texture::TextureType type); + static VkImageViewType getImageViewType(gfx::texture::TextureType type); +}; + +} // namespace arch::gfx::vulkan diff --git a/src/platform/vulkan/buffer/VulkanBufferManager.cpp b/src/platform/vulkan/buffer/VulkanBufferManager.cpp new file mode 100644 index 0000000..995e8ba --- /dev/null +++ b/src/platform/vulkan/buffer/VulkanBufferManager.cpp @@ -0,0 +1,19 @@ +#include "VulkanBufferManager.h" + +namespace arch::gfx::vulkan::buffer { + +VulkanBufferManager::VulkanBufferManager(const Ref& context): _context(context) {} + +Ref VulkanBufferManager::_createVertexBufferImpl(void* data, u32 size) { + return nullptr; +} + +Ref VulkanBufferManager::_createIndexBufferImpl(std::span indices) { + return nullptr; +} + +Ref VulkanBufferManager::_createBufferImpl(void* data, u32 size) { + return nullptr; +} + +} // namespace arch::gfx::vulkan::buffer diff --git a/src/platform/vulkan/buffer/VulkanBufferManager.h b/src/platform/vulkan/buffer/VulkanBufferManager.h new file mode 100644 index 0000000..afe0ec9 --- /dev/null +++ b/src/platform/vulkan/buffer/VulkanBufferManager.h @@ -0,0 +1,22 @@ +#pragma once + +#include "../VulkanContext.h" +#include "gfx/Buffer.h" + +namespace arch::gfx::vulkan::buffer { + +class VulkanBufferManager final: public BufferManager { +public: + explicit VulkanBufferManager(const Ref& context); + ~VulkanBufferManager() noexcept override = default; + +private: + Ref _createVertexBufferImpl(void* data, u32 size) override; + Ref _createIndexBufferImpl(std::span indices) override; + Ref _createBufferImpl(void* data, u32 size) override; + +private: + Ref _context; +}; + +} // namespace arch::gfx::vulkan::buffer diff --git a/src/platform/vulkan/exceptions/VulkanException.cpp b/src/platform/vulkan/exceptions/VulkanException.cpp new file mode 100644 index 0000000..4e68003 --- /dev/null +++ b/src/platform/vulkan/exceptions/VulkanException.cpp @@ -0,0 +1,8 @@ +#include "VulkanException.h" + +namespace arch::gfx::vulkan::exceptions { + +VulkanException::VulkanException(const std::string& message, const std::source_location& location): + Exception("Vulkan", message, location) {} + +} // namespace arch::gfx::vulkan::exceptions diff --git a/src/platform/vulkan/exceptions/VulkanException.h b/src/platform/vulkan/exceptions/VulkanException.h new file mode 100644 index 0000000..b5c4447 --- /dev/null +++ b/src/platform/vulkan/exceptions/VulkanException.h @@ -0,0 +1,12 @@ +#pragma once + +#include "Exception.h" + +namespace arch::gfx::vulkan::exceptions { + +class VulkanException final: public Exception { +public: + VulkanException(const std::string& message, const std::source_location& location = std::source_location::current()); +}; + +} // namespace arch::gfx::vulkan::exceptions diff --git a/src/platform/vulkan/texture/VulkanImage.cpp b/src/platform/vulkan/texture/VulkanImage.cpp new file mode 100644 index 0000000..13f686c --- /dev/null +++ b/src/platform/vulkan/texture/VulkanImage.cpp @@ -0,0 +1,130 @@ +#include "VulkanImage.h" + +#include "../VulkanContext.h" +#include "../VulkanUtils.h" + +namespace arch::gfx::vulkan::texture { + +VulkanImage::VulkanImage( + const Ref& context, + gfx::texture::TextureType type, + uint3 size, + u32 mipMapLevels, + VkFormat format, + VkImageTiling tiling, + VkImageUsageFlags usage, + VkMemoryPropertyFlags memoryFlags +): + _type(type), + _memoryFlags(memoryFlags), + _format(format), + _extent(size.x, size.y, size.z), + _mipMapLevels(mipMapLevels) { + VkImageCreateInfo imageInfo = { + .sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO, + .pNext = nullptr, + .flags = 0, + .imageType = VulkanUtils::getType(type), + .format = _format, + .extent = _extent, + + .mipLevels = 1, + .arrayLayers = 1, + + // Anti-aliasing + .samples = VK_SAMPLE_COUNT_1_BIT, + + // Tailing + .tiling = tiling, + .usage = usage, + }; + VulkanUtils::vkAssert( + vkCreateImage(context->getDevice(), &imageInfo, context->getAllocator(), &_image), + "Failed to create image." + ); + + vkGetImageMemoryRequirements(context->getDevice(), _image, &_memoryRequirements); + + i32 memType = context->findMemoryType(_memoryRequirements.memoryTypeBits, _memoryFlags); + if (memType < 0) { + throw exceptions::VulkanException("Failed to find suitable memory type."); + } + + // Allocate memory + VkMemoryAllocateInfo allocInfo = { + .sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO, + .allocationSize = _memoryRequirements.size, + .memoryTypeIndex = (u32)memType, + }; + VulkanUtils::vkAssert( + vkAllocateMemory(context->getDevice(), &allocInfo, context->getAllocator(), &_memory), + "Failed to allocate image memory." + ); + + // Bind the memory + VulkanUtils::vkAssert(vkBindImageMemory(context->getDevice(), _image, _memory, 0), "Failed to bind image memory."); +} + +VulkanImage::~VulkanImage() { + if (_image || _imageView || _memory) { + Logger::warn("Image was not destroyed properly. Make sure to call cleanup() before destroying the object."); + } +} + +void VulkanImage::transitionImageLayout(VkImageLayout oldLayout, VkImageLayout newLayout) {} + +void VulkanImage::createImageView(const Ref& context, u16 layerCount, i32 layerIndex, VkFormat format) { + VkImageAspectFlags aspectFlags = VK_IMAGE_ASPECT_COLOR_BIT; + + if (format == VK_FORMAT_D32_SFLOAT) { + aspectFlags = VK_IMAGE_ASPECT_DEPTH_BIT; + } else if (format == VK_FORMAT_D24_UNORM_S8_UINT || format == VK_FORMAT_D32_SFLOAT_S8_UINT) { + aspectFlags = VK_IMAGE_ASPECT_DEPTH_BIT | VK_IMAGE_ASPECT_STENCIL_BIT; + } + + VkImageViewCreateInfo viewInfo = { + .sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO, + .image = _image, + .viewType = VulkanUtils::getImageViewType(_type), + .format = format, + .components = { + .r = VK_COMPONENT_SWIZZLE_IDENTITY, + .g = VK_COMPONENT_SWIZZLE_IDENTITY, + .b = VK_COMPONENT_SWIZZLE_IDENTITY, + .a = VK_COMPONENT_SWIZZLE_IDENTITY, + }, + .subresourceRange = { + .aspectMask = aspectFlags, + .baseMipLevel = 0, + .levelCount = _mipMapLevels, + .baseArrayLayer = 0, + .layerCount = VK_REMAINING_ARRAY_LAYERS, + } + }; + + VulkanUtils::vkAssert( + vkCreateImageView(context->getDevice(), &viewInfo, context->getAllocator(), &_imageView), + "Failed to create image view." + ); +} + +void VulkanImage::cleanup(const Ref& context) { + if (_imageView) { + vkDestroyImageView(context->getDevice(), _imageView, context->getAllocator()); + _imageView = nullptr; + } + + if (_memory) { + vkFreeMemory(context->getDevice(), _memory, context->getAllocator()); + _memory = nullptr; + } + + if (_image) { + vkDestroyImage(context->getDevice(), _image, context->getAllocator()); + _image = nullptr; + } + + _memoryRequirements = {}; +} + +} // namespace arch::gfx::vulkan::texture diff --git a/src/platform/vulkan/texture/VulkanImage.h b/src/platform/vulkan/texture/VulkanImage.h new file mode 100644 index 0000000..4f6f7f2 --- /dev/null +++ b/src/platform/vulkan/texture/VulkanImage.h @@ -0,0 +1,64 @@ +#pragma once + +#include "Mmath.h" +#include "Ref.h" +#include "gfx/texture/TextureType.h" +#include + +namespace arch::gfx::vulkan { +class VulkanContext; +} // namespace arch::gfx::vulkan + +namespace arch::gfx::vulkan::texture { + +class VulkanImage { +public: + + VulkanImage( + const Ref& context, + gfx::texture::TextureType type, + uint3 size, + u32 mipMapLevels, + VkFormat format, + VkImageTiling tiling, + VkImageUsageFlags usage, + VkMemoryPropertyFlags memoryFlags + ); + ~VulkanImage(); + + void transitionImageLayout(VkImageLayout oldLayout, VkImageLayout newLayout); + void createImageView(const Ref& context, u16 layerCount, i32 layerIndex, VkFormat format); + + void cleanup(const Ref& context); + +public: + VkImageView getImageView() const { return _imageView; } + + VkImage getImage() const { return _image; } + + VkDeviceMemory getMemory() const { return _memory; } + + VkMemoryRequirements getMemoryRequirements() const { return _memoryRequirements; } + + VkFormat getFormat() const { return _format; } + + VkExtent3D getExtent() const { return _extent; } + + u32 getMipMapLevels() const { return _mipMapLevels; } + +private: + gfx::texture::TextureType _type; + + VkImage _image = nullptr; + VkDeviceMemory _memory = nullptr; + VkImageView _imageView = nullptr; + + VkMemoryRequirements _memoryRequirements{}; + + VkMemoryPropertyFlags _memoryFlags; + + VkFormat _format; + VkExtent3D _extent; + u32 _mipMapLevels; +}; +} // namespace arch::gfx::vulkan::texture diff --git a/src/platform/vulkan/texture/VulkanTexture.cpp b/src/platform/vulkan/texture/VulkanTexture.cpp new file mode 100644 index 0000000..4b5295a --- /dev/null +++ b/src/platform/vulkan/texture/VulkanTexture.cpp @@ -0,0 +1,53 @@ +#include "VulkanTexture.h" + +#include "../VulkanRenderer.h" +#include "../VulkanUtils.h" +#include "gfx/Renderer.h" + +namespace arch::gfx::vulkan::texture { + +VulkanTexture::VulkanTexture( + const Ref& context, + uint3 size, + TextureType type, + GraphicsFormat format, + TextureWrapMode wrapMode, + TextureFilterMode filterMode, + bool isReadable, + VkImageUsageFlags usage +): + Texture(format, wrapMode, filterMode, isReadable), + _context(context), + _image( + context, + type, + size, + 1, + VulkanUtils::getFormat(format), + VK_IMAGE_TILING_LINEAR, + usage, + VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT + ) {} + +VulkanTexture::~VulkanTexture() { + _image.cleanup(_context.lock()); +} + +void VulkanTexture::setFilter(TextureFilterMode filterMode) { + Texture::setFilter(filterMode); +} + +void VulkanTexture::setWrap(TextureWrapMode wrapMode) { + Texture::setWrap(wrapMode); +} + +uint3 VulkanTexture::getSize() const { + VkExtent3D extent = _image.getExtent(); + return { extent.width, extent.height, extent.depth }; +} + +VulkanImage& VulkanTexture::getImage() { + return _image; +} + +} // namespace arch::gfx::vulkan::texture diff --git a/src/platform/vulkan/texture/VulkanTexture.h b/src/platform/vulkan/texture/VulkanTexture.h new file mode 100644 index 0000000..0d3dea9 --- /dev/null +++ b/src/platform/vulkan/texture/VulkanTexture.h @@ -0,0 +1,40 @@ +#pragma once + +#include "VulkanImage.h" +#include "gfx/Texture.h" +#include + +namespace arch::gfx::vulkan { +class VulkanContext; +} + +namespace arch::gfx::vulkan::texture { + +class VulkanTexture final: public Texture { +public: + VulkanTexture( + const Ref& context, + uint3 size, + TextureType type, + GraphicsFormat format, + TextureWrapMode wrapMode, + TextureFilterMode filterMode, + bool isReadable, + VkImageUsageFlags usage = VK_IMAGE_USAGE_SAMPLED_BIT + ); + ~VulkanTexture() override; + + void setFilter(TextureFilterMode filterMode) override; + void setWrap(TextureWrapMode wrapMode) override; + uint3 getSize() const override; + + VulkanImage& getImage(); + +private: + WeakRef _context; + VulkanImage _image; + + friend class VulkanTextureManager; +}; + +} // namespace arch::gfx::vulkan::texture diff --git a/src/platform/vulkan/texture/VulkanTextureManager.cpp b/src/platform/vulkan/texture/VulkanTextureManager.cpp new file mode 100644 index 0000000..6c39185 --- /dev/null +++ b/src/platform/vulkan/texture/VulkanTextureManager.cpp @@ -0,0 +1,32 @@ +#include "VulkanTextureManager.h" + +#include "../VulkanRenderer.h" +#include "../texture/VulkanTexture.h" + +namespace arch::gfx::vulkan::texture { + +VulkanTextureManager::VulkanTextureManager(const Ref& context): TextureManager(), _context(context) {} + +Ref VulkanTextureManager::_createTexture2DImpl( + u32 width, + u32 height, + GraphicsFormat format, + void* data, + TextureWrapMode wrapMode, + TextureFilterMode filterMode, + bool isReadable +) { + Ref texture = createRef( + _context, + uint3{ width, height, 1 }, + TextureType::texture2D, + format, + wrapMode, + filterMode, + isReadable + ); + + return texture; +} + +} // namespace arch::gfx::vulkan::texture diff --git a/src/platform/vulkan/texture/VulkanTextureManager.h b/src/platform/vulkan/texture/VulkanTextureManager.h new file mode 100644 index 0000000..32806fa --- /dev/null +++ b/src/platform/vulkan/texture/VulkanTextureManager.h @@ -0,0 +1,28 @@ +#pragma once +#include "../VulkanContext.h" +#include "gfx/Texture.h" + +namespace arch::gfx::vulkan::texture { + +class VulkanTextureManager final: public TextureManager { +public: + explicit VulkanTextureManager(const Ref& renderer); + ~VulkanTextureManager() noexcept override = default; + +protected: + Ref _createTexture2DImpl( + u32 width, + u32 height, + GraphicsFormat format, + void* data, + TextureWrapMode wrapMode, + TextureFilterMode filterMode, + bool isReadable + ) override; + +private: + Ref _context; + friend class VulkanRenderer; +}; + +} // namespace arch::gfx::vulkan::texture diff --git a/src/Scene.cpp b/src/scene/Scene.cpp similarity index 92% rename from src/Scene.cpp rename to src/scene/Scene.cpp index 9d3e66f..e492ba4 100644 --- a/src/Scene.cpp +++ b/src/scene/Scene.cpp @@ -1,6 +1,6 @@ -#include +#include "scene/Scene.h" -namespace arch { +namespace arch::scene { Scene::Scene() noexcept: _domain{} { const auto root = _domain.newEntity(); @@ -43,4 +43,4 @@ const Scene::Node& Scene::rootNode() const noexcept { return *_rootNode; } -} // namespace arch +} // namespace arch::scene diff --git a/src/scene/SceneManager.cpp b/src/scene/SceneManager.cpp new file mode 100644 index 0000000..04f14ba --- /dev/null +++ b/src/scene/SceneManager.cpp @@ -0,0 +1,42 @@ +#include "scene/SceneManager.h" + +#include "Ecs.h" +#include "exceptions/InitException.h" +#include "scene/Components.h" + +namespace arch::scene { + +SceneManager::SceneManager() {} + +void SceneManager::update() { + // TODO: update scene & all systems +} + +void SceneManager::renderScene(const Ref& renderer) { + if (_currentScene) { + auto view = _currentScene->domain().view(); + + for (auto [entity, transform, mesh] : view.all()) { + renderer->render(mesh.mesh, transform.getTransformMatrix()); + } + } +} + +const Ref& SceneManager::currentScene() const noexcept { + return _currentScene; +} + +void SceneManager::changeScene(const Ref& scene) { + // TODO: destroy old scene + + _currentScene = scene; + + // TODO: init new scene +} + +Ref& SceneManager::get() { + static Ref instance = createRef(); + return instance; +} + +} // namespace arch::scene diff --git a/tests/Scene_Test.cpp b/tests/Scene_Test.cpp index 47cde64..bdff25d 100644 --- a/tests/Scene_Test.cpp +++ b/tests/Scene_Test.cpp @@ -4,7 +4,7 @@ #include TEST(Scene, HierarchyIteration) { - arch::Scene scene; + arch::scene::Scene scene; auto e = std::array(); @@ -25,16 +25,20 @@ TEST(Scene, HierarchyIteration) { { e[8]->setParent(*e[7]); } e[9]->setParent(*e[0]); - ASSERT_TRUE(std::ranges::equal( - e, - scene.rootNode().recursiveIterable(), - {}, - [](const arch::hier::HierarchyNode* node) { return node->entity(); } - )); - ASSERT_TRUE(std::ranges::equal( - std::views::reverse(e), - std::views::reverse(scene.rootNode().recursiveIterable()), - {}, - [](const arch::hier::HierarchyNode* node) { return node->entity(); } - )); + ASSERT_TRUE( + std::ranges::equal( + e, + scene.rootNode().recursiveIterable(), + {}, + [](const arch::hier::HierarchyNode* node) { return node->entity(); } + ) + ); + ASSERT_TRUE( + std::ranges::equal( + std::views::reverse(e), + std::views::reverse(scene.rootNode().recursiveIterable()), + {}, + [](const arch::hier::HierarchyNode* node) { return node->entity(); } + ) + ); }