Compare commits

...

44 Commits

Author SHA1 Message Date
Anish Bhobe 84f38e18ed Cleanup with namespaces. 2025-06-01 21:40:34 +02:00
Anish Bhobe 299665a0fd Resolve include ambiguity with tools. 2025-06-01 19:42:57 +02:00
Anish Bhobe 38b697f202 Move all buffers to DeviceAddress. 2025-06-01 19:24:31 +02:00
Anish Bhobe 19e3222460 Rename systems::Device to RenderingDevice to avoid ambiguity. 2025-05-31 21:42:14 +02:00
Anish Bhobe 4cdb39c6ba Change name for pipeline creation. 2025-05-31 11:51:21 +02:00
Anish Bhobe cc1fd12b64 fix: Async Compute 2025-05-28 20:58:00 +02:00
Anish Bhobe cfb76c7d78 East Const supremacy. 2025-05-27 18:52:04 +02:00
Anish Bhobe 58edfef94d General Cleanup. 2025-05-27 18:30:50 +02:00
Anish Bhobe 3ca3beb1e4 fix: Improper GGX usage. 2025-05-25 21:29:08 +02:00
Anish Bhobe 4f71df797c Model Render updated. 2025-05-25 19:23:19 +02:00
Anish Bhobe befa36c7f1 ContextPool support for unordered contexts. 2025-05-18 00:06:06 +02:00
Anish Bhobe 3b4ea52611 ContextPools for Frames. 2025-05-17 15:25:33 +02:00
Anish Bhobe 8e2c77bcf1 Clean-up `flake.nix`. 2025-05-13 15:58:42 +02:00
Anish Bhobe 1f8f102ee1 Fixes added for clang.
- Enum values now assigned with C-enums instead of type-safe enums.
- Atomic included at `constants.h` so it's available everywhere.
- Fixed CommitManager forward declaration.
- Added `scalarLayout` option to slang compiler.
2025-05-13 13:00:11 +02:00
Anish Bhobe cc4cffe989 Fix: Usage of `format_to` in `fmt`.
Should not be using versions.
`v11::format_to` -> `fmt::format_to`.
2025-05-13 12:46:58 +02:00
Anish Bhobe adfa86ebe9 fix: obnoxious error of "util/logger". 2025-05-10 18:01:22 +02:00
Anish Bhobe 41c91058b6 Shader reflection added. 2025-05-10 18:00:25 +02:00
Anish Bhobe 3a7a2b4ab7 [WIP] Box moved to 'new API' pending fixes. 2025-05-08 17:44:55 +02:00
Anish Bhobe 63282c3587 [WIP] Added a transfer context for uploads. 2025-05-08 00:34:59 +02:00
Anish Bhobe 7351415ebf Consolidate Present as a special submit. 2025-05-07 18:27:13 +02:00
Anish Bhobe 1db942f1a9 Remove Cast and Recast. 2025-05-07 17:44:01 +02:00
Anish Bhobe 3dc6501246 fix: Error on window resize. 2025-05-06 19:06:04 +02:00
Anish Bhobe 5d6ddbb158 Added slang for Shader code compilation.
TODO: Use slang to create descriptors.
2025-05-06 15:32:58 +02:00
Anish Bhobe 7507394af9 Added Pipeline creation into the Device. 2025-05-03 13:46:44 +02:00
Anish Bhobe 2facb3e6c1 fix: Context memory leak. 2025-05-02 20:32:15 +02:00
Anish Bhobe d683de3181 Draw Triangle and bug-fixes. 2025-05-01 20:05:31 +02:00
Anish Bhobe d82e81d104 Begin Consolidation all objects under the systems::Device interface.
Currently clears a screen.
- Merge all resource creation API under Device.
- Begin a basic Context setup.
2025-05-01 13:27:19 +02:00
Anish Bhobe a790c26f1c Rename Context to Instance. 2025-04-28 21:37:03 +02:00
Anish Bhobe 668189acb5 Fixed bug in Model Loading.
Model Loader was loading indexes into image instead of going via texture.
TODO: Textures also have samplers.
2025-04-10 23:50:57 +02:00
Anish Bhobe b8b620a723 Triangle is ready. 2025-04-09 20:33:38 +02:00
Anish Bhobe 703624eb86 Reworked buffer types. 2025-04-08 23:33:07 +02:00
Anish Bhobe 1748a48272 Image, View and Sampler are all updated. 2025-04-07 00:21:50 +02:00
Anish Bhobe d8770c1e06 [WIP] Updated Buffers.
TODO: Update Image and Views.
2025-04-06 21:02:58 +02:00
Anish Bhobe 1bee73e46f [WIP] Move to shared_ptr. 2025-04-06 19:31:12 +02:00
Anish Bhobe 98660a11fa Rename: ImageViewManager -> ViewManager 2025-04-02 22:48:24 +02:00
Anish Bhobe 8eb5a678fc Cleanup and header re-date. 2025-04-02 22:46:30 +02:00
Anish Bhobe e5b002c8cc Rename freelist and clean up code. 2025-04-02 21:56:49 +02:00
Anish Bhobe ec6aeb6f3b Fixed Commit count issue. 2025-04-02 21:55:05 +02:00
Anish Bhobe 8f9b6d66be At par with old-arch.
FIXED: Bug with black speckles in prefilter.
Caused by MipMapping enabled.
2025-04-02 21:08:14 +02:00
Anish Bhobe aa729610cf [WIP] Fixed texture load corruption issue. 2025-04-02 00:27:57 +02:00
Anish Bhobe 3ab9d838fa [WIP] Separated ImageViews. 2025-04-01 08:54:30 +02:00
Anish Bhobe 73c96dc56b [WIP] Moving ModelRender to new arch.
TODO: ImageView
2025-03-31 21:32:11 +02:00
Anish Bhobe afec1e3e32 Reimplemented RenderResourceManager. 2025-03-24 22:31:47 +01:00
Anish Bhobe 396810d203 RenderResourceManager handles images and bindless. 2025-03-02 19:19:43 +01:00
208 changed files with 17269 additions and 7249 deletions

1
.gitignore vendored
View File

@ -5,3 +5,4 @@ build/
.direnv/ .direnv/
.ccls-cache/ .ccls-cache/
*.user *.user
/vcpkg_installed

View File

@ -4,12 +4,12 @@ cmake_minimum_required(VERSION 3.13)
project(Aster VERSION 0.1.0) project(Aster VERSION 0.1.0)
set(CMAKE_CXX_STANDARD 20) set(CMAKE_CXX_STANDARD 23)
set(CMAKE_CXX_STANDARD_REQUIRED ON) set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_CXX_EXTENSIONS OFF) set(CMAKE_CXX_EXTENSIONS OFF)
if (MSVC) if (MSVC)
set(CMAKE_CXX_FLAGS "/W4 /GR- ${MSVC_FLAGS}") set(CMAKE_CXX_FLAGS "/W4 /GR- ${MSVC_FLAGS} /utf-8")
set(CMAKE_CXX_FLAGS_RELEASE "/O3") set(CMAKE_CXX_FLAGS_RELEASE "/O3")
add_compile_definitions(_HAS_EXCEPTIONS=0) add_compile_definitions(_HAS_EXCEPTIONS=0)
add_compile_definitions(_CRT_SECURE_NO_WARNINGS) add_compile_definitions(_CRT_SECURE_NO_WARNINGS)

View File

@ -1,30 +1,27 @@
function(add_shader TARGET SHADER) function(add_shader TARGET SHADER)
find_package(Vulkan REQUIRED COMPONENTS dxc) find_package(Vulkan REQUIRED COMPONENTS dxc)
get_filename_component(vulkan-bin-dir ${Vulkan_GLSLC_EXECUTABLE} DIRECTORY)
find_program(slangc_exe NAMES "slangc")
if (NOT slangc_exe STREQUAL "slangc_exe-NOTFOUND")
set(slangc_exe_FOUND true)
endif()
get_filename_component(shader-ext ${SHADER} LAST_EXT) get_filename_component(shader-ext ${SHADER} LAST_EXT)
get_filename_component(shader-inner ${SHADER} NAME_WLE)
get_filename_component(shader-type ${shader-inner} LAST_EXT)
string(REPLACE "." "" shader-type ${shader-type})
set(current-shader-path ${CMAKE_CURRENT_SOURCE_DIR}/${SHADER}) set(current-shader-path ${CMAKE_CURRENT_SOURCE_DIR}/${SHADER})
set(current-output-path ${CMAKE_CURRENT_BINARY_DIR}/${SHADER}.spv) set(current-output-path ${CMAKE_CURRENT_BINARY_DIR}/${SHADER}.slang-module)
set(current-copy-path ${CMAKE_CURRENT_BINARY_DIR}/${SHADER})
get_filename_component(current-output-dir ${current-output-path} DIRECTORY) get_filename_component(current-output-dir ${current-output-path} DIRECTORY)
file(MAKE_DIRECTORY ${current-output-dir}) file(MAKE_DIRECTORY ${current-output-dir})
if (Vulkan_dxc_exe_FOUND AND ${shader-ext} STREQUAL ".hlsl") if (${shader-ext} STREQUAL ".slang")
message("Marked as hlsl file. ${current-output-path}")
add_custom_command( add_custom_command(
OUTPUT ${current-output-path} OUTPUT ${current-output-path} ${current-copy-path}
COMMAND Vulkan::dxc_exe ${DXC_SHADER_FLAGS} -spirv -T "${shader-type}_6_0" -E main ${current-shader-path} -Fo ${current-output-path} COMMAND ${slangc_exe} ${current-shader-path} -o ${current-output-path}
DEPENDS ${current-shader-path} COMMAND ${CMAKE_COMMAND} -E copy ${current-shader-path} ${current-copy-path}
IMPLICIT_DEPENDS CXX ${current-shader-path}
VERBATIM)
elseif (Vulkan_glslc_FOUND AND ${shader-ext} STREQUAL ".glsl")
message("Marked as glsl file. ${current-output-path}")
add_custom_command(
OUTPUT ${current-output-path}
COMMAND Vulkan::glslc ${GLSLC_SHADER_FLAGS} -o ${current-output-path} ${current-shader-path}
DEPENDS ${current-shader-path} DEPENDS ${current-shader-path}
IMPLICIT_DEPENDS CXX ${current-shader-path} IMPLICIT_DEPENDS CXX ${current-shader-path}
VERBATIM) VERBATIM)
@ -33,10 +30,4 @@ function(add_shader TARGET SHADER)
# Make sure our build depends on this output. # Make sure our build depends on this output.
set_source_files_properties(${current-output-path} PROPERTIES GENERATED TRUE) set_source_files_properties(${current-output-path} PROPERTIES GENERATED TRUE)
target_sources(${TARGET} PRIVATE ${current-output-path}) target_sources(${TARGET} PRIVATE ${current-output-path})
endfunction(add_shader) endfunction(add_shader)
function(add_shaders TARGET SHADERS)
foreach(shader IN ${SHADERS})
add_shader(TARGET ${shader})
endforeach()
endfunction(add_shaders)

View File

@ -9,15 +9,16 @@ find_package(Vulkan REQUIRED)
find_package(fmt CONFIG REQUIRED) find_package(fmt CONFIG REQUIRED)
find_package(VulkanMemoryAllocator CONFIG REQUIRED) find_package(VulkanMemoryAllocator CONFIG REQUIRED)
find_package(EASTL CONFIG REQUIRED) find_package(EASTL CONFIG REQUIRED)
find_library(slang NAMES "slang" CONFIG REQUIRED)
find_package(foonathan_memory CONFIG REQUIRED)
add_library(aster_core STATIC) add_library(aster_core STATIC)
add_subdirectory("include") add_subdirectory("include")
add_subdirectory("src") add_subdirectory("src")
set_property(TARGET aster_core PROPERTY CXX_STANDARD 20) target_compile_features(aster_core PUBLIC cxx_std_23)
target_include_directories(aster_core PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}/include/aster")
target_include_directories(aster_core PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}/include") target_include_directories(aster_core PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}/include")
target_link_libraries(aster_core PUBLIC glm::glm-header-only) target_link_libraries(aster_core PUBLIC glm::glm-header-only)
@ -26,4 +27,6 @@ target_include_directories(aster_core PRIVATE ${SCOTTT_DEBUGBREAK_INCLUDE_DIRS})
target_link_libraries(aster_core PRIVATE fmt::fmt) target_link_libraries(aster_core PRIVATE fmt::fmt)
target_link_libraries(aster_core PRIVATE EASTL) target_link_libraries(aster_core PRIVATE EASTL)
target_link_libraries(aster_core PUBLIC Vulkan::Headers GPUOpen::VulkanMemoryAllocator) target_link_libraries(aster_core PUBLIC Vulkan::Headers GPUOpen::VulkanMemoryAllocator)
target_link_libraries(aster_core PUBLIC ${slang})
target_link_libraries(aster_core PRIVATE foonathan_memory)

View File

@ -7,6 +7,6 @@ add_subdirectory("systems")
add_subdirectory("util") add_subdirectory("util")
target_sources(aster_core target_sources(aster_core
PUBLIC "aster.h") PUBLIC "aster.h" "import_types.h")
target_precompile_headers(aster_core PUBLIC "aster.h") target_precompile_headers(aster_core PUBLIC "aster.h")

View File

@ -5,4 +5,11 @@
#pragma once #pragma once
#include "core/global.h" #include "aster/core/global.h"
#include "aster/core/window.h"
#include "aster/systems/commit_manager.h"
#include "aster/systems/context.h"
#include "aster/systems/rendering_device.h"
#include "aster/systems/resource.h"

View File

@ -7,7 +7,7 @@ INTERFACE
"global.h" "global.h"
"constants.h" "constants.h"
"config.h" "config.h"
"context.h" "instance.h"
"physical_device.h" "physical_device.h"
"device.h" "device.h"
"swapchain.h" "swapchain.h"
@ -15,7 +15,9 @@ INTERFACE
"queue_allocation.h" "queue_allocation.h"
"buffer.h" "buffer.h"
"image.h" "image.h"
"image_view.h"
"surface.h" "surface.h"
"size.h" "size.h"
"type_traits.h" "type_traits.h"
"window.h") "window.h"
"sampler.h")

View File

@ -1,133 +1,121 @@
// ============================================= // =============================================
// Aster: buffer.h // Aster: buffer.h
// Copyright (c) 2020-2024 Anish Bhobe // Copyright (c) 2020-2025 Anish Bhobe
// ============================================= // =============================================
#pragma once #pragma once
#include "global.h" #include "global.h"
namespace aster
{
struct Device; struct Device;
// TODO Refactor the Buffer Hierarchy /// A Vulkan buffer wrapper.
struct Buffer struct Buffer
{ {
enum class FlagBits : u8
{
eNone = 0x0,
eStaging = 0x1,
eUniform = 0x2,
eStorage = 0x4,
eIndex = 0x8,
eVertex = 0x10,
eIndirect = 0x20,
};
using Flags = vk::Flags<FlagBits>;
constexpr static Flags FLAGS = {};
Device const *m_Device = nullptr;
vk::Buffer m_Buffer = nullptr; vk::Buffer m_Buffer = nullptr;
VmaAllocation m_Allocation = nullptr; VmaAllocation m_Allocation = nullptr;
u8 *m_Mapped = nullptr; ///< If the buffer is host visible, it should be (and stay) mapped.
uptr m_DeviceAddr = 0;
usize m_Size = 0;
Flags m_Flags = {};
// If the buffer is host visible, it should be (and stay) mapped. /// @returns True if it is a valid vulkan buffer.
u8 *m_Mapped = nullptr; [[nodiscard]] bool
IsValid() const
{
return m_Buffer;
}
[[nodiscard]] usize GetSize() const; /// If the buffer is host visible, it should be (and stay) mapped.
[[nodiscard]] bool IsHostVisible() const; /// @returns True if the buffer is host-visible and mapped.
[[nodiscard]] bool IsValid() const; [[nodiscard]] bool
[[nodiscard]] bool IsMapped() const; IsMapped() const
[[nodiscard]] bool IsOwned() const; {
[[nodiscard]] bool IsCommitted() const; return m_Mapped;
void SetCommitted(bool committed); }
void Destroy(const Device *device); /// Writes the data to the buffer.
void Write(const Device *device, usize offset, usize size, const void *data); /// @note The buffer must be mapped.
void Write(usize offset, usize size, void const *data) const;
void Allocate(const Device *device, usize size, vk::BufferUsageFlags bufferUsage, /// If Buffer Device Address is enabled,
VmaAllocationCreateFlags allocationFlags, VmaMemoryUsage memoryUsage, cstr name); /// Get a pointer.
[[nodiscard]] uptr GetDeviceAddress() const;
uptr // Constructors
GetDeviceAddress(const Device *device);
// Buffer.size is used for bookkeeping Buffer(Device const *device, usize size, vk::BufferUsageFlags bufferUsage, VmaAllocationCreateFlags allocationFlags,
// If the buffer is Invalid, the remaining data in Buffer is used intrusively by `GpuResourceManager`. VmaMemoryUsage memoryUsage, cstr name);
usize m_Size_ = 0;
constexpr static usize VALID_BUFFER_BIT = Cast<usize>(1llu << 63); Buffer(Buffer &&other) noexcept;
constexpr static usize OWNED_BIT = 1llu << 62; Buffer &operator=(Buffer &&other) noexcept;
constexpr static usize COMMITTED_BIT = 1llu << 61;
constexpr static usize SIZE_MASK = ~(VALID_BUFFER_BIT | OWNED_BIT | COMMITTED_BIT); ~Buffer();
DISALLOW_COPY_AND_ASSIGN(Buffer);
}; };
template <>
constexpr bool concepts::GpuResource<Buffer> = true;
// Ensure that m_Size doesn't get used intrusively since it manages the state.
static_assert(offsetof(Buffer, m_Size_) > sizeof(usize));
struct UniformBuffer : Buffer struct UniformBuffer : Buffer
{ {
void Init(const Device *device, usize size, cstr name = nullptr); constexpr static Flags FLAGS = FlagBits::eUniform;
}; };
struct StorageBuffer : Buffer struct StorageBuffer : Buffer
{ {
void Init(const Device *device, usize size, bool hostVisible, cstr name = nullptr); constexpr static Flags FLAGS = FlagBits::eStorage;
void Init(const Device *device, usize size, bool hostVisible, bool deviceAddress, cstr name = nullptr);
}; };
struct IndirectBuffer : Buffer struct IndirectBuffer : Buffer
{ {
void Init(const Device *device, usize size, bool hostVisible, cstr name = nullptr); constexpr static Flags FLAGS = FlagBits::eIndirect;
};
struct StorageIndexBuffer : StorageBuffer
{
void Init(const Device *device, usize size, bool hostVisible, bool deviceAddress, cstr name = nullptr);
}; };
struct VertexBuffer : Buffer struct VertexBuffer : Buffer
{ {
void Init(const Device *device, usize size, cstr name = nullptr); constexpr static Flags FLAGS = FlagBits::eVertex;
void Write(const Device *device, void *data, usize size, usize offset) const = delete;
}; };
struct IndexBuffer : Buffer struct IndexBuffer : Buffer
{ {
void Init(const Device *device, usize size, cstr name = nullptr); constexpr static Flags FLAGS = FlagBits::eIndex;
void Write(const Device *device, void *data, usize size, usize offset) const = delete;
}; };
struct StagingBuffer : Buffer struct StagingBuffer : Buffer
{ {
void Init(const Device *device, usize size, cstr name = nullptr); constexpr static Flags FLAGS = FlagBits::eStaging;
}; };
inline usize namespace concepts
Buffer::GetSize() const
{ {
return m_Size_ & SIZE_MASK; template <typename T>
} concept AnyBuffer = std::derived_from<T, Buffer>;
inline bool template <typename T, typename TInto>
Buffer::IsHostVisible() const concept BufferInto = std::derived_from<T, Buffer> and std::derived_from<TInto, Buffer> and
{ (static_cast<bool>(T::FLAGS & TInto::FLAGS) or std::same_as<Buffer, TInto>);
return IsMapped();
}
inline bool template <typename T>
Buffer::IsValid() const concept AnyBufferRef = Deref<T> and AnyBuffer<DerefType<T>>;
{
return m_Size_ & VALID_BUFFER_BIT;
}
inline bool template <typename T, typename TTo>
Buffer::IsMapped() const concept BufferRefTo = Deref<T> and BufferInto<DerefType<T>, TTo>;
{
return m_Mapped;
}
inline bool } // namespace concepts
Buffer::IsOwned() const } // namespace aster
{
return m_Size_ & OWNED_BIT;
}
inline bool
Buffer::IsCommitted() const
{
return m_Size_ & COMMITTED_BIT;
}
inline void
Buffer::SetCommitted(const bool committed)
{
m_Size_ = committed ? (m_Size_ | COMMITTED_BIT) : (m_Size_ & ~COMMITTED_BIT);
}

View File

@ -1,6 +1,6 @@
// ============================================= // =============================================
// Aster: config.h // Aster: config.h
// Copyright (c) 2020-2024 Anish Bhobe // Copyright (c) 2020-2025 Anish Bhobe
// ============================================= // =============================================
#pragma once #pragma once
@ -15,6 +15,8 @@
#define VULKAN_HPP_NO_STRUCT_CONSTRUCTORS #define VULKAN_HPP_NO_STRUCT_CONSTRUCTORS
#define VULKAN_HPP_DISABLE_ENHANCED_MODE 1 #define VULKAN_HPP_DISABLE_ENHANCED_MODE 1
#define VULKAN_HPP_NO_EXCEPTIONS 1 #define VULKAN_HPP_NO_EXCEPTIONS 1
#define VULKAN_HPP_NO_SMART_HANDLE 1
#define VULKAN_HPP_NO_STRUCT_SETTERS 1
#define VMA_STATIC_VULKAN_FUNCTIONS 0 #define VMA_STATIC_VULKAN_FUNCTIONS 0
#define VMA_DYNAMIC_VULKAN_FUNCTIONS 1 #define VMA_DYNAMIC_VULKAN_FUNCTIONS 1
@ -25,4 +27,4 @@
#define USE_OPTICK (0) #define USE_OPTICK (0)
#else #else
#define USE_OPTICK (1) #define USE_OPTICK (1)
#endif #endif

View File

@ -1,6 +1,6 @@
// ============================================= // =============================================
// Aster: constants.h // Aster: constants.h
// Copyright (c) 2020-2024 Anish Bhobe // Copyright (c) 2020-2025 Anish Bhobe
// ============================================= // =============================================
#pragma once #pragma once
@ -13,6 +13,10 @@
#include <glm/glm.hpp> #include <glm/glm.hpp>
#include <glm/gtx/quaternion.hpp> #include <glm/gtx/quaternion.hpp>
#include <atomic>
namespace aster::types
{
using c8 = char; using c8 = char;
using u8 = uint8_t; using u8 = uint8_t;
using u16 = uint16_t; using u16 = uint16_t;
@ -28,58 +32,32 @@ using f128 = long double;
using b8 = bool; using b8 = bool;
using b32 = u32; using b32 = u32;
using usize = size_t; using usize = size_t;
using isize = intptr_t;
using uptr = uintptr_t; using uptr = uintptr_t;
using cstr = const char *; using cstr = char const *;
namespace ansi_color
{
constexpr auto Black = "\u001b[30m";
constexpr auto Red = "\u001b[31m";
constexpr auto Green = "\u001b[32m";
constexpr auto Yellow = "\u001b[33m";
constexpr auto Blue = "\u001b[34m";
constexpr auto Magenta = "\u001b[35m";
constexpr auto Cyan = "\u001b[36m";
constexpr auto White = "\u001b[37m";
constexpr auto Reset = "\u001b[0m";
} // namespace ansi_color
template <typename TType, typename TFrom>
constexpr auto
Cast(TFrom &&in)
{
return static_cast<TType>(std::forward<TFrom>(in));
}
template <typename TType, typename TFrom>
constexpr auto
Recast(TFrom &&in)
{
return reinterpret_cast<TType>(std::forward<TFrom>(in));
}
constexpr f32 constexpr f32
operator""_deg(long double degrees) operator""_deg(long double degrees)
{ {
return glm::radians<f32>(Cast<f32>(degrees)); return glm::radians<f32>(static_cast<f32>(degrees));
} }
constexpr f32 constexpr f32
operator""_deg(unsigned long long int degrees) operator""_deg(unsigned long long int degrees)
{ {
return glm::radians<f32>(Cast<f32>(degrees)); return glm::radians<f32>(static_cast<f32>(degrees));
} }
constexpr f32 constexpr f32
operator""_rad(long double rads) operator""_rad(long double rads)
{ {
return Cast<f32>(rads); return static_cast<f32>(rads);
} }
constexpr f32 constexpr f32
operator""_rad(unsigned long long int rads) operator""_rad(unsigned long long int rads)
{ {
return Cast<f32>(rads); return static_cast<f32>(rads);
} }
using glm::ivec2; using glm::ivec2;
@ -94,57 +72,6 @@ using glm::mat2;
using glm::mat3; using glm::mat3;
using glm::mat4; using glm::mat4;
constexpr auto *PROJECT_NAME = "Aster";
struct Version
{
u8 m_Major;
u8 m_Minor;
u8 m_Patch;
[[nodiscard]] u32
GetVkVersion() const
{
return VK_MAKE_API_VERSION(0, m_Major, m_Minor, m_Patch);
}
};
constexpr Version VERSION = {
.m_Major = 0,
.m_Minor = 1,
.m_Patch = 0,
};
constexpr u32
Kilobyte(const u32 in)
{
return in * 1024;
}
constexpr usize
Kilobyte(const usize in)
{
return in * 1024;
}
constexpr u32
Megabyte(const u32 in)
{
return in * 1024 * 1024;
}
constexpr usize
Megabyte(const usize in)
{
return in * 1024 * 1024;
}
constexpr usize
Gigabyte(const usize in)
{
return in * 1024 * 1024 * 1024;
}
template <typename T> template <typename T>
constexpr T MaxValue = std::numeric_limits<T>::max(); constexpr T MaxValue = std::numeric_limits<T>::max();
@ -168,3 +95,22 @@ constexpr T Qnan = std::numeric_limits<T>::quiet_NaN();
template <typename T> template <typename T>
constexpr T Snan = std::numeric_limits<T>::signalling_NaN(); constexpr T Snan = std::numeric_limits<T>::signalling_NaN();
} // namespace aster::types
namespace aster
{
using namespace types;
namespace ansi_color
{
constexpr auto Black = "\u001b[30m";
constexpr auto Red = "\u001b[31m";
constexpr auto Green = "\u001b[32m";
constexpr auto Yellow = "\u001b[33m";
constexpr auto Blue = "\u001b[34m";
constexpr auto Magenta = "\u001b[35m";
constexpr auto Cyan = "\u001b[36m";
constexpr auto White = "\u001b[37m";
constexpr auto Reset = "\u001b[0m";
} // namespace ansi_color
} // namespace aster

View File

@ -1,17 +1,19 @@
// ============================================= // =============================================
// Aster: device.h // Aster: device.h
// Copyright (c) 2020-2024 Anish Bhobe // Copyright (c) 2020-2025 Anish Bhobe
// ============================================= // =============================================
#pragma once #pragma once
#include "global.h" #include "global.h"
#include <EASTL/vector.h>
#include <EASTL/span.h> #include <EASTL/span.h>
#include <EASTL/vector.h>
namespace aster
{
struct QueueAllocation; struct QueueAllocation;
struct Context; struct Instance;
struct PhysicalDevice; struct PhysicalDevice;
struct Features struct Features
@ -31,19 +33,31 @@ struct Device final
vk::PipelineCache m_PipelineCache = nullptr; vk::PipelineCache m_PipelineCache = nullptr;
bool m_ValidationEnabled = true; bool m_ValidationEnabled = true;
template <typename T> template <concepts::VkHandle T>
requires vk::isVulkanHandleType<T>::value void SetName(const T &object, cstr name) const; void SetName(T const &object, cstr name) const;
[[nodiscard]] vk::Queue GetQueue(u32 familyIndex, u32 queueIndex) const; [[nodiscard]] vk::Queue GetQueue(u32 familyIndex, u32 queueIndex) const;
[[nodiscard]] eastl::vector<u8> DumpPipelineCache() const; [[nodiscard]] eastl::vector<u8> DumpPipelineCache() const;
void WaitIdle() const; void WaitIdle() const;
vk::Device *
operator->()
{
return &m_Device;
}
vk::Device const *
operator->() const
{
return &m_Device;
}
// Ctor/Dtor // Ctor/Dtor
Device(const Context *context, PhysicalDevice *physicalDevice, Features *enabledFeatures, Device() = default;
const eastl::vector<QueueAllocation> &queueAllocations, NameString &&name); Device(Instance const &context, PhysicalDevice &physicalDevice, Features &enabledFeatures,
Device(const Context *context, PhysicalDevice *physicalDevice, Features *enabledFeatures, eastl::span<QueueAllocation> const &queueAllocations, eastl::span<u8> const &pipelineCacheData,
const eastl::vector<QueueAllocation> &queueAllocations, eastl::span<u8> &&pipelineCacheData, NameString &&name); NameString &&name);
~Device(); ~Device();
// Move // Move
@ -53,15 +67,15 @@ struct Device final
DISALLOW_COPY_AND_ASSIGN(Device); DISALLOW_COPY_AND_ASSIGN(Device);
}; };
template <typename T> template <concepts::VkHandle T>
requires vk::isVulkanHandleType<T>::value void void
Device::SetName(const T &object, cstr name) const Device::SetName(T const &object, cstr name) const
{ {
if (!m_ValidationEnabled || !name || !object) if (!m_ValidationEnabled || !name || !object)
return; return;
auto handle = Recast<u64>(Cast<typename T::NativeType>(object)); auto handle = reinterpret_cast<u64>(static_cast<typename T::NativeType>(object));
const vk::DebugUtilsObjectNameInfoEXT objectNameInfo = { vk::DebugUtilsObjectNameInfoEXT const objectNameInfo = {
.objectType = object.objectType, .objectType = object.objectType,
.objectHandle = handle, .objectHandle = handle,
.pObjectName = name, .pObjectName = name,
@ -70,3 +84,4 @@ Device::SetName(const T &object, cstr name) const
WARN_IF(Failed(result), "Could not name {:x}: {} as {}. Cause: {}", handle, to_string(object.objectType), name, WARN_IF(Failed(result), "Could not name {:x}: {} as {}. Cause: {}", handle, to_string(object.objectType), name,
result); result);
} }
} // namespace aster

View File

@ -1,17 +1,20 @@
// ============================================= // =============================================
// Aster: global.h // Aster: global.h
// Copyright (c) 2020-2024 Anish Bhobe // Copyright (c) 2020-2025 Anish Bhobe
// ============================================= // =============================================
#pragma once #pragma once
#include "config.h" #include "config.h"
#include "constants.h" #include "constants.h"
#include "util/logger.h"
#include "aster/util/logger.h"
#include <GLFW/glfw3.h> #include <GLFW/glfw3.h>
#include <glm/glm.hpp> #include <glm/glm.hpp>
#include <EASTL/shared_ptr.h>
#include <fmt/format.h> #include <fmt/format.h>
// Macros that can collide with functions. // Macros that can collide with functions.
@ -26,6 +29,8 @@
#if !defined(NDEBUG) #if !defined(NDEBUG)
#define VULKAN_HPP_ASSERT(expr) DEBUG_IF(!(expr), "Vulkan assert failed") #define VULKAN_HPP_ASSERT(expr) DEBUG_IF(!(expr), "Vulkan assert failed")
#endif #endif
#include "EASTL/intrusive_ptr.h"
#include "type_traits.h" #include "type_traits.h"
#include <EASTL/fixed_string.h> #include <EASTL/fixed_string.h>
@ -34,8 +39,6 @@
#include <vulkan/vulkan.hpp> #include <vulkan/vulkan.hpp>
constexpr u32 ASTER_API_VERSION = VK_API_VERSION_1_3;
#define CODE_LOC " @ " __FILE__ ":" VULKAN_HPP_STRINGIFY(__LINE__) #define CODE_LOC " @ " __FILE__ ":" VULKAN_HPP_STRINGIFY(__LINE__)
#define DISALLOW_COPY_AND_ASSIGN(CLASS_NAME) \ #define DISALLOW_COPY_AND_ASSIGN(CLASS_NAME) \
@ -50,38 +53,113 @@ constexpr u32 ASTER_API_VERSION = VK_API_VERSION_1_3;
#define Take(ELEMENT) eastl::exchange(ELEMENT, {}) #define Take(ELEMENT) eastl::exchange(ELEMENT, {})
#define TODO(MSG) assert(false && ("Unimplemented: " MSG)) #define TODO(...) assert(!("Unimplemented: " __VA_ARGS__))
#define FIX(...) static_assert(!("Unimplemented: " __VA_ARGS__))
#define UNREACHABLE(...) assert(!("Unreachable: " __VA_ARGS__))
#define AbortIfFailed(RESULT) \
do \
{ \
vk::Result _checkResultValue_; \
ERROR_IF(Failed(_checkResultValue_ = static_cast<vk::Result>(RESULT)), "Cause: {}", _checkResultValue_) \
THEN_ABORT(_checkResultValue_); \
} while (false)
#define AbortIfFailedMV(RESULT, MSG, EXTRA) \
do \
{ \
vk::Result _checkResultValue_; \
ERROR_IF(Failed(_checkResultValue_ = static_cast<vk::Result>(RESULT)), MSG " Cause: {}", EXTRA, \
_checkResultValue_) \
THEN_ABORT(_checkResultValue_); \
} while (false)
#define AbortIfFailedM(RESULT, MSG) \
do \
{ \
auto _checkResultValue_ = static_cast<vk::Result>(RESULT); \
ERROR_IF(Failed(_checkResultValue_), MSG " Cause: {}", _checkResultValue_) THEN_ABORT(_checkResultValue_); \
} while (false)
[[nodiscard]] inline bool [[nodiscard]] inline bool
Failed(const vk::Result result) Failed(vk::Result const result)
{ {
return result != vk::Result::eSuccess; return result != vk::Result::eSuccess;
} }
namespace aster::concepts
{
template <typename T>
concept VkHandle = vk::isVulkanHandleType<T>::value;
}
namespace aster
{
constexpr u32 ASTER_API_VERSION = VK_API_VERSION_1_3;
using NameString = eastl::fixed_string<char, 32, false>; using NameString = eastl::fixed_string<char, 32, false>;
template <typename TFlagBits> struct Version
struct eastl::hash<vk::Flags<TFlagBits>> // NOLINT(*-dcl58-cpp)
{ {
[[nodiscard]] usize u8 m_Major;
operator()(const vk::Flags<TFlagBits> &val) u8 m_Minor;
u8 m_Patch;
[[nodiscard]] u32
GetVkVersion() const
{ {
return std::hash<u32>()(Cast<u32>(val)); return VK_MAKE_API_VERSION(0, m_Major, m_Minor, m_Patch);
} }
}; };
constexpr Version VERSION = {
.m_Major = 0,
.m_Minor = 1,
.m_Patch = 0,
};
constexpr u32
Kilobyte(u32 const in)
{
return in * 1024;
}
constexpr usize
Kilobyte(usize const in)
{
return in * 1024;
}
constexpr u32
Megabyte(u32 const in)
{
return in * 1024 * 1024;
}
constexpr usize
Megabyte(usize const in)
{
return in * 1024 * 1024;
}
constexpr usize
Gigabyte(usize const in)
{
return in * 1024 * 1024 * 1024;
}
template <typename T> template <typename T>
[[nodiscard]] usize [[nodiscard]] usize
HashAny(const T &val) HashAny(T const &val)
{ {
return eastl::hash<std::remove_cvref_t<T>>()(val); return eastl::hash<std::remove_cvref_t<T>>()(val);
} }
[[nodiscard]] inline usize [[nodiscard]] inline usize
HashCombine(const usize hash0, const usize hash1) HashCombine(usize const hash0, usize const hash1)
{ {
constexpr usize saltValue = 0x9e3779b9; constexpr usize saltValue = 0x9e3779b9;
const usize tempVar = hash1 + saltValue + (hash0 << 6) + (hash0 >> 2); usize const tempVar = hash1 + saltValue + (hash0 << 6) + (hash0 >> 2);
return hash0 ^ tempVar; return hash0 ^ tempVar;
} }
@ -104,32 +182,32 @@ struct Time
Update() Update()
{ {
ERROR_IF(std::isnan(m_Elapsed), "Time not init."); ERROR_IF(std::isnan(m_Elapsed), "Time not init.");
const auto newElapsed = glfwGetTime(); auto const newElapsed = glfwGetTime();
m_Delta = std::clamp(newElapsed - m_Elapsed, 0.0, MAX_DELTA); m_Delta = std::clamp(newElapsed - m_Elapsed, 0.0, MAX_DELTA);
m_Elapsed = newElapsed; m_Elapsed = newElapsed;
} }
}; };
[[nodiscard]] constexpr usize [[nodiscard]] constexpr usize
ClosestMultiple(const usize val, const usize of) ClosestMultiple(usize const val, usize const of)
{ {
return of * ((val + of - 1) / of); return of * ((val + of - 1) / of);
} }
[[nodiscard]] constexpr u32 [[nodiscard]] constexpr u32
ClosestMultiple(const u32 val, const u32 of) ClosestMultiple(u32 const val, u32 const of)
{ {
return of * ((val + of - 1) / of); return of * ((val + of - 1) / of);
} }
[[nodiscard]] constexpr bool [[nodiscard]] constexpr bool
IsPowerOfTwo(const usize val) IsPowerOfTwo(usize const val)
{ {
return val && !(val & (val - 1)); return val && !(val & (val - 1));
} }
[[nodiscard]] constexpr bool [[nodiscard]] constexpr bool
IsPowerOfTwo(const u32 val) IsPowerOfTwo(u32 const val)
{ {
return val && !(val & (val - 1)); return val && !(val & (val - 1));
} }
@ -149,10 +227,10 @@ ClosestLargerPowerOfTwo(usize val)
} }
[[nodiscard]] constexpr usize [[nodiscard]] constexpr usize
ClosestPowerOfTwo(const usize val) ClosestPowerOfTwo(usize const val)
{ {
const usize largerPo2 = ClosestLargerPowerOfTwo(val); usize const largerPo2 = ClosestLargerPowerOfTwo(val);
const usize smallerPo2 = largerPo2 >> 1; usize const smallerPo2 = largerPo2 >> 1;
return (smallerPo2 + largerPo2 <= (val << 1)) ? largerPo2 : smallerPo2; return (smallerPo2 + largerPo2 <= (val << 1)) ? largerPo2 : smallerPo2;
} }
@ -170,10 +248,10 @@ ClosestLargerPowerOfTwo(u32 val)
} }
[[nodiscard]] constexpr u32 [[nodiscard]] constexpr u32
ClosestPowerOfTwo(const u32 val) ClosestPowerOfTwo(u32 const val)
{ {
const u32 largerPo2 = ClosestLargerPowerOfTwo(val); u32 const largerPo2 = ClosestLargerPowerOfTwo(val);
const u32 smallerPo2 = largerPo2 >> 1; u32 const smallerPo2 = largerPo2 >> 1;
return (smallerPo2 + largerPo2 <= (val << 1)) ? largerPo2 : smallerPo2; return (smallerPo2 + largerPo2 <= (val << 1)) ? largerPo2 : smallerPo2;
} }
@ -189,25 +267,47 @@ GetMaskOffset(u32 val)
return count; return count;
} }
template <> template <typename T>
struct fmt::formatter<vk::Result> : nested_formatter<std::string> concept VkToString = requires(T a) {
{ vk::to_string(a) } -> std::convertible_to<std::string>;
};
template <typename T>
using Ref = eastl::shared_ptr<T>;
template <typename T>
using WeakRef = eastl::weak_ptr<T>;
} // namespace aster
template <aster::VkToString T>
struct fmt::formatter<T> : nested_formatter<std::string>
{ {
auto auto
// ReSharper disable once CppInconsistentNaming // ReSharper disable once CppInconsistentNaming
format(vk::Result result, format_context &ctx) const format(T result, format_context &ctx) const
{ {
return write_padded(ctx, return write_padded(ctx,
[this, result](auto out) { return v10::format_to(out, "{}", nested(to_string(result))); }); [this, result](auto out) { return fmt::format_to(out, "{}", nested(to_string(result))); });
} }
}; };
template <typename TType, usize TCount, bool TOverflow> template <typename TType, aster::usize TCount, bool TOverflow>
struct fmt::formatter<eastl::fixed_string<TType, TCount, TOverflow>> : nested_formatter<cstr> struct fmt::formatter<eastl::fixed_string<TType, TCount, TOverflow>> : nested_formatter<aster::cstr>
{ {
auto auto
// ReSharper disable once CppInconsistentNaming // ReSharper disable once CppInconsistentNaming
format(const eastl::fixed_string<TType, TCount, TOverflow> &str, format_context &ctx) const format(eastl::fixed_string<TType, TCount, TOverflow> const &str, format_context &ctx) const
{ {
return write_padded(ctx, [this, str](auto out) { return v10::format_to(out, "{}", nested(str.c_str())); }); return write_padded(ctx, [this, str](auto out) { return fmt::format_to(out, "{}", nested(str.c_str())); });
} }
}; };
template <typename TFlagBits>
struct eastl::hash<vk::Flags<TFlagBits>> // NOLINT(*-dcl58-cpp)
{
[[nodiscard]] aster::usize
operator()(vk::Flags<TFlagBits> const &val)
{
return std::hash<aster::u32>()(static_cast<aster::u32>(val));
}
};

View File

@ -1,137 +1,135 @@
// ============================================= // =============================================
// Aster: image.h // Aster: image.h
// Copyright (c) 2020-2024 Anish Bhobe // Copyright (c) 2020-2025 Anish Bhobe
// ============================================= // =============================================
#pragma once #pragma once
#include "global.h" #include "global.h"
namespace aster
{
struct StorageTexture;
struct Device; struct Device;
[[nodiscard]] inline vk::Extent2D [[nodiscard]] inline vk::Extent2D
ToExtent2D(const vk::Extent3D &extent) ToExtent2D(vk::Extent3D const &extent)
{ {
return {extent.width, extent.height}; return {extent.width, extent.height};
} }
[[nodiscard]] inline vk::Extent3D [[nodiscard]] inline vk::Extent3D
ToExtent3D(const vk::Extent2D &extent, const u32 depth) ToExtent3D(vk::Extent2D const &extent, u32 const depth)
{ {
return {extent.width, extent.height, depth}; return {extent.width, extent.height, depth};
} }
[[nodiscard]] inline vk::Offset2D [[nodiscard]] inline vk::Offset2D
ToOffset2D(const vk::Extent3D &extent) ToOffset2D(vk::Extent3D const &extent)
{ {
return {Cast<i32>(extent.width), Cast<i32>(extent.height)}; return {static_cast<i32>(extent.width), static_cast<i32>(extent.height)};
} }
[[nodiscard]] inline vk::Offset3D [[nodiscard]] inline vk::Offset3D
ToOffset3D(const vk::Extent3D &extent) ToOffset3D(vk::Extent3D const &extent)
{ {
return {Cast<i32>(extent.width), Cast<i32>(extent.height), Cast<i32>(extent.depth)}; return {static_cast<i32>(extent.width), static_cast<i32>(extent.height), static_cast<i32>(extent.depth)};
} }
struct Image struct Image
{ {
enum class FlagBits : u8
{
eSampled = 0x1,
eStorage = 0x2,
eCube = 0x4,
};
using Flags = vk::Flags<FlagBits>;
constexpr static Flags FLAGS = {};
Device const *m_Device = nullptr;
vk::Image m_Image = nullptr; vk::Image m_Image = nullptr;
vk::ImageView m_View = nullptr;
VmaAllocation m_Allocation = nullptr; VmaAllocation m_Allocation = nullptr;
vk::Extent3D m_Extent; vk::Extent3D m_Extent;
// Image.m_MipLevels_ is used for bookkeeping vk::Format m_Format;
// If the image is Invalid, the remaining data in Image is used intrusively by `GpuResourceManager`.
u8 m_EmptyPadding_ = 0; u8 m_EmptyPadding_ = 0;
u8 m_Flags_ = 0; Flags m_Flags_ = {};
u8 m_LayerCount = 0; u8 m_LayerCount = 0;
u8 m_MipLevels = 0; u8 m_MipLevels = 0;
[[nodiscard]] bool IsValid() const; [[nodiscard]] bool
[[nodiscard]] bool IsOwned() const; IsValid() const
[[nodiscard]] u32 GetMipLevels() const; {
[[nodiscard]] bool IsCommitted() const; return m_Image;
void SetCommitted(bool committed); }
void Destroy(const Device *device); [[nodiscard]] u32
GetMipLevels() const
{
return m_MipLevels;
}
constexpr static u8 VALID_BIT = 1u << 7; void DestroyView(vk::ImageView imageView) const;
constexpr static u8 OWNED_BIT = 1u << 6;
constexpr static u8 COMMITTED_BIT = 1u << 5; // Constructors.
explicit Image(Device const *device, vk::Image image, VmaAllocation allocation, vk::Extent3D extent,
vk::Format format, Flags flags, u8 layerCount, u8 mipLevels);
Image(Image &&other) noexcept;
Image &operator=(Image &&other) noexcept;
~Image();
DISALLOW_COPY_AND_ASSIGN(Image);
}; };
template <>
constexpr bool concepts::GpuResource<Image> = true;
struct Texture : Image struct Texture : Image
{ {
void Init(const Device *device, vk::Extent2D extent, vk::Format imageFormat, bool isMipMapped, cstr name = nullptr); constexpr static Flags FLAGS = FlagBits::eSampled;
}; };
static_assert(sizeof(Texture) == sizeof(Image)); struct ImageCube : Image
struct TextureCube : Texture
{ {
void constexpr static Flags FLAGS = FlagBits::eCube;
Init(const Device *device, u32 cubeSide, vk::Format imageFormat, bool isMipMapped = false, cstr name = nullptr);
}; };
static_assert(sizeof(TextureCube) == sizeof(Image)); struct TextureCube : Image
struct AttachmentImage : Image
{ {
void Init(const Device *device, vk::Extent2D extent, vk::Format imageFormat, cstr name = nullptr); constexpr static Flags FLAGS = Texture::FLAGS | ImageCube::FLAGS;
}; };
static_assert(sizeof(AttachmentImage) == sizeof(Image)); struct StorageImage : Image
struct DepthImage : Image
{ {
void Init(const Device *device, vk::Extent2D extent, cstr name = nullptr); constexpr static Flags FLAGS = FlagBits::eStorage;
}; };
static_assert(sizeof(DepthImage) == sizeof(Image)); struct StorageTexture : StorageImage
struct StorageTexture : Texture
{ {
void Init(const Device *device, vk::Extent2D extent, vk::Format imageFormat, bool isSampled, cstr name = nullptr); constexpr static Flags FLAGS = StorageImage::FLAGS | Texture::FLAGS;
}; };
static_assert(sizeof(StorageTexture) == sizeof(Image)); struct StorageTextureCube : StorageImage
struct StorageTextureCube : StorageTexture
{ {
void Init(const Device *device, u32 cubeSide, vk::Format imageFormat, bool isSampled, bool isMipMapped = false, constexpr static Flags FLAGS = StorageImage::FLAGS | Texture::FLAGS | ImageCube::FLAGS;
cstr name = nullptr);
}; };
static_assert(sizeof(StorageTextureCube) == sizeof(Image)); namespace concepts
inline bool
Image::IsValid() const
{ {
return m_Flags_ & VALID_BIT;
}
inline bool template <typename T>
Image::IsOwned() const concept AnyImage = std::derived_from<T, Image>;
{
return m_Flags_ & OWNED_BIT;
}
inline u32 template <typename T, typename TInto>
Image::GetMipLevels() const concept ImageInto = std::derived_from<T, Image> and std::derived_from<TInto, Image> and
{ (static_cast<bool>(T::FLAGS & TInto::FLAGS) or std::same_as<Image, TInto>);
return m_MipLevels;
}
inline bool template <typename T>
Image::IsCommitted() const concept AnyImageRef = Deref<T> and AnyImage<DerefType<T>>;
{
return m_Flags_ & COMMITTED_BIT;
}
inline void template <typename T, typename TTo>
Image::SetCommitted(const bool committed) concept ImageRefTo = Deref<T> and ImageInto<DerefType<T>, TTo>;
{
m_Flags_ = committed ? (m_Flags_ | COMMITTED_BIT) : (m_Flags_ & ~COMMITTED_BIT); } // namespace concepts
} } // namespace aster

View File

@ -0,0 +1,111 @@
// =============================================
// Aster: image_view.h
// Copyright (c) 2020-2025 Anish Bhobe
// =============================================
#pragma once
#include "global.h"
#include "image.h"
namespace aster
{
template <concepts::AnyImage TImage>
struct View
{
using ImageType = TImage;
Ref<Image> m_Image;
vk::ImageView m_View = nullptr;
vk::Extent3D m_Extent;
u8 m_BaseLayer = 0;
u8 m_LayerCount = 0;
u8 m_BaseMipLevel = 0;
u8 m_MipLevelCount = 0;
[[nodiscard]] vk::Image
GetImage() const
{
return m_Image->m_Image;
}
[[nodiscard]] bool
IsValid() const
{
return static_cast<bool>(m_Image);
}
View(Ref<Image> image, vk::ImageView const view, vk::Extent3D const extent, u8 const baseLayer, u8 const layerCount,
u8 const baseMipLevel, u8 const mipLevelCount)
: m_Image{std::move(image)}
, m_View{view}
, m_Extent{extent}
, m_BaseLayer{baseLayer}
, m_LayerCount{layerCount}
, m_BaseMipLevel{baseMipLevel}
, m_MipLevelCount{mipLevelCount}
{
}
View(View &&other) noexcept
: m_Image{std::move(other.m_Image)}
, m_View{Take(other.m_View)}
, m_Extent{std::move(other.m_Extent)}
, m_BaseLayer{other.m_BaseLayer}
, m_LayerCount{other.m_LayerCount}
, m_BaseMipLevel{other.m_BaseMipLevel}
, m_MipLevelCount{other.m_MipLevelCount}
{
}
View &
operator=(View &&other) noexcept
{
if (this == &other)
return *this;
using std::swap;
swap(m_Image, other.m_Image);
swap(m_View, other.m_View);
swap(m_Extent, other.m_Extent);
swap(m_BaseLayer, other.m_BaseLayer);
swap(m_LayerCount, other.m_LayerCount);
swap(m_BaseMipLevel, other.m_BaseMipLevel);
swap(m_MipLevelCount, other.m_MipLevelCount);
return *this;
}
DISALLOW_COPY_AND_ASSIGN(View);
~View()
{
if (!IsValid())
return;
m_Image->DestroyView(Take(m_View));
}
};
using ImageView = View<Image>;
using ImageCubeView = View<ImageCube>;
using TextureView = View<Texture>;
using TextureCubeView = View<TextureCube>;
using StorageImageView = View<StorageImage>;
using StorageTextureView = View<StorageTexture>;
using StorageTextureCubeView = View<StorageTextureCube>;
namespace concepts
{
template <typename T>
concept View = std::derived_from<T, View<typename T::ImageType>>;
template <typename T, typename TTo>
concept ViewTo = View<T> and ImageInto<typename T::ImageType, TTo>;
template <typename T>
concept ViewRef = Deref<T> and View<DerefType<T>>;
template <typename T, typename TTo>
concept ViewRefTo = ViewRef<T> and ImageInto<typename DerefType<T>::ImageType, TTo>;
} // namespace concepts
} // namespace aster

View File

@ -1,32 +1,35 @@
// ============================================= // =============================================
// Aster: context.h // Aster: context.h
// Copyright (c) 2020-2024 Anish Bhobe // Copyright (c) 2020-2025 Anish Bhobe
// ============================================= // =============================================
#pragma once #pragma once
#include "global.h" #include "global.h"
namespace aster
{
/** /**
* @class Context * @class Instance
* *
* @brief Vulkan context to handle device initialization logic. * @brief Vulkan context to handle device initialization logic.
* *
* Handles the required hardware interactions. * Handles the required hardware interactions.
*/ */
struct Context final struct Instance final
{ {
// Members // Members
vk::Instance m_Instance = nullptr; vk::Instance m_Instance = nullptr;
vk::DebugUtilsMessengerEXT m_DebugMessenger = nullptr; vk::DebugUtilsMessengerEXT m_DebugMessenger = nullptr;
// Ctor/Dtor // Ctor/Dtor
Context(cstr appName, Version version, bool enableValidation = ENABLE_LAYER_MESSAGES_DEFAULT_VALUE); Instance() = default;
~Context(); Instance(cstr appName, Version version, bool enableValidation = ENABLE_LAYER_MESSAGES_DEFAULT_VALUE);
~Instance();
// Move // Move
Context(Context &&other) noexcept; Instance(Instance &&other) noexcept;
Context &operator=(Context &&other) noexcept; Instance &operator=(Instance &&other) noexcept;
#if !defined(ASTER_NDEBUG) #if !defined(ASTER_NDEBUG)
constexpr static bool ENABLE_LAYER_MESSAGES_DEFAULT_VALUE = true; constexpr static bool ENABLE_LAYER_MESSAGES_DEFAULT_VALUE = true;
@ -34,5 +37,6 @@ struct Context final
constexpr static bool ENABLE_LAYER_MESSAGES_DEFAULT_VALUE = false; constexpr static bool ENABLE_LAYER_MESSAGES_DEFAULT_VALUE = false;
#endif #endif
DISALLOW_COPY_AND_ASSIGN(Context); DISALLOW_COPY_AND_ASSIGN(Instance);
}; };
} // namespace aster

View File

@ -1,6 +1,6 @@
// ============================================= // =============================================
// Aster: physical_device.h // Aster: physical_device.h
// Copyright (c) 2020-2024 Anish Bhobe // Copyright (c) 2020-2025 Anish Bhobe
// ============================================= // =============================================
#pragma once #pragma once
@ -8,10 +8,14 @@
#include "global.h" #include "global.h"
#include "surface.h" #include "surface.h"
#include <sstream>
#include <EASTL/fixed_vector.h> #include <EASTL/fixed_vector.h>
namespace aster
{
struct Window; struct Window;
struct Context; struct Instance;
enum class QueueSupportFlagBits enum class QueueSupportFlagBits
{ {
@ -23,6 +27,31 @@ enum class QueueSupportFlagBits
using QueueSupportFlags = vk::Flags<QueueSupportFlagBits>; using QueueSupportFlags = vk::Flags<QueueSupportFlagBits>;
inline std::string
// ReSharper disable once CppInconsistentNaming
format_as(QueueSupportFlags const &qfi)
{
std::stringstream sb;
if (qfi & QueueSupportFlagBits::eGraphics)
{
sb << "Graphics | ";
}
if (qfi & QueueSupportFlagBits::eTransfer)
{
sb << "Transfer | ";
}
if (qfi & QueueSupportFlagBits::eCompute)
{
sb << "Compute | ";
}
if (qfi & QueueSupportFlagBits::ePresent)
{
sb << "Present | ";
}
auto const sbv = sb.view();
return std::string(sbv.substr(0, sbv.size() - 3));
}
struct QueueFamilyInfo struct QueueFamilyInfo
{ {
u32 m_Index; u32 m_Index;
@ -30,6 +59,12 @@ struct QueueFamilyInfo
QueueSupportFlags m_Support; QueueSupportFlags m_Support;
}; };
inline std::string
format_as(QueueFamilyInfo const &qfi)
{
return fmt::format("Queue {}: Count={} Support={}", qfi.m_Index, qfi.m_Count, qfi.m_Support);
}
[[nodiscard]] vk::SurfaceCapabilitiesKHR [[nodiscard]] vk::SurfaceCapabilitiesKHR
GetSurfaceCapabilities(vk::PhysicalDevice physicalDevice, vk::SurfaceKHR surface); GetSurfaceCapabilities(vk::PhysicalDevice physicalDevice, vk::SurfaceKHR surface);
@ -48,11 +83,13 @@ struct PhysicalDevice final
eastl::vector<vk::PresentModeKHR> m_PresentModes; eastl::vector<vk::PresentModeKHR> m_PresentModes;
eastl::vector<QueueFamilyInfo> m_QueueFamilies; eastl::vector<QueueFamilyInfo> m_QueueFamilies;
PhysicalDevice() = default;
PhysicalDevice(vk::SurfaceKHR surface, vk::PhysicalDevice physicalDevice); PhysicalDevice(vk::SurfaceKHR surface, vk::PhysicalDevice physicalDevice);
}; };
class PhysicalDevices : public eastl::fixed_vector<PhysicalDevice, 4> class PhysicalDevices : public eastl::fixed_vector<PhysicalDevice, 4>
{ {
public: public:
PhysicalDevices(const Surface *surface, const Context *context); PhysicalDevices(Surface const &surface, Instance const &context);
}; };
} // namespace aster

View File

@ -1,6 +1,6 @@
// ============================================= // =============================================
// Aster: pipeline.h // Aster: pipeline.h
// Copyright (c) 2020-2024 Anish Bhobe // Copyright (c) 2020-2025 Anish Bhobe
// ============================================= // =============================================
#pragma once #pragma once
@ -9,16 +9,52 @@
#include <EASTL/vector.h> #include <EASTL/vector.h>
namespace aster
{
struct Device; struct Device;
struct Pipeline struct Pipeline
{ {
const Device *m_Device; enum class Kind
vk::PipelineLayout m_Layout; {
vk::Pipeline m_Pipeline; eGraphics,
eastl::vector<vk::DescriptorSetLayout> m_SetLayouts; eCompute,
};
Pipeline(const Device *device, vk::PipelineLayout layout, vk::Pipeline pipeline, Device const *m_Device = nullptr;
eastl::vector<vk::DescriptorSetLayout> &&setLayouts); vk::PipelineLayout m_Layout;
vk::Pipeline m_Pipeline = nullptr;
eastl::vector<vk::DescriptorSetLayout> m_SetLayouts;
Kind m_Kind;
Pipeline() = default;
Pipeline(Device const *device, vk::PipelineLayout layout, vk::Pipeline pipeline,
eastl::vector<vk::DescriptorSetLayout> &&setLayouts, Kind kind);
~Pipeline(); ~Pipeline();
};
DISALLOW_COPY_AND_ASSIGN(Pipeline);
Pipeline(Pipeline &&other) noexcept
: m_Device{other.m_Device}
, m_Layout{Take(other.m_Layout)}
, m_Pipeline{Take(other.m_Pipeline)}
, m_SetLayouts{std::move(other.m_SetLayouts)}
, m_Kind{other.m_Kind}
{
}
Pipeline &
operator=(Pipeline &&other) noexcept
{
if (this == &other)
return *this;
using eastl::swap;
swap(m_Device, other.m_Device);
swap(m_Layout, other.m_Layout);
swap(m_Pipeline, other.m_Pipeline);
swap(m_SetLayouts, other.m_SetLayouts);
swap(m_Kind, other.m_Kind);
return *this;
}
};
} // namespace aster

View File

@ -1,14 +1,17 @@
// ============================================= // =============================================
// Aster: queue_allocation.h // Aster: queue_allocation.h
// Copyright (c) 2020-2024 Anish Bhobe // Copyright (c) 2020-2025 Anish Bhobe
// ============================================= // =============================================
#pragma once #pragma once
#include "global.h" #include "global.h"
namespace aster
{
struct QueueAllocation struct QueueAllocation
{ {
u32 m_Family; u32 m_Family;
u32 m_Count; u32 m_Count;
}; };
} // namespace aster

View File

@ -0,0 +1,35 @@
// =============================================
// Aster: sampler.h
// Copyright (c) 2020-2025 Anish Bhobe
// =============================================
#pragma once
#include "global.h"
namespace aster
{
struct Device;
struct Sampler final
{
Device const *m_Device = nullptr;
vk::Sampler m_Sampler = nullptr;
[[nodiscard]] bool
IsValid() const
{
return m_Sampler;
}
// Constructors
Sampler(Device const *device, vk::SamplerCreateInfo const &samplerCreateInfo, cstr name);
~Sampler();
Sampler(Sampler &&other) noexcept;
Sampler &operator=(Sampler &&other) noexcept;
DISALLOW_COPY_AND_ASSIGN(Sampler);
};
} // namespace aster

View File

@ -1,20 +1,46 @@
// ============================================= // =============================================
// Aster: size.h // Aster: size.h
// Copyright (c) 2020-2024 Anish Bhobe // Copyright (c) 2020-2025 Anish Bhobe
// ============================================= // =============================================
#pragma once #pragma once
#include "global.h" #include "global.h"
namespace aster
{
struct Size2D struct Size2D
{ {
u32 m_Width; u32 m_Width = 0;
u32 m_Height; u32 m_Height = 0;
Size2D() = default;
Size2D(u32 const width, u32 const height)
: m_Width{width}
, m_Height{height}
{
}
Size2D(vk::Extent2D const extent)
: m_Width{extent.width}
, m_Height{extent.height}
{
}
Size2D &
operator=(vk::Extent2D const other)
{
m_Height = other.height;
m_Width = other.width;
return *this;
}
bool operator==(Size2D const &) const = default;
explicit
operator vk::Extent2D() const operator vk::Extent2D() const
{ {
return {m_Width, m_Height}; return {m_Width, m_Height};
} }
}; };
} // namespace aster

View File

@ -1,23 +1,26 @@
// ============================================= // =============================================
// Aster: surface.h // Aster: surface.h
// Copyright (c) 2020-2024 Anish Bhobe // Copyright (c) 2020-2025 Anish Bhobe
// ============================================= // =============================================
#pragma once #pragma once
#include "global.h" #include "global.h"
struct Context; namespace aster
{
struct Instance;
struct Window; struct Window;
struct Surface struct Surface
{ {
Context *m_Context; Instance *m_Context;
vk::SurfaceKHR m_Surface; vk::SurfaceKHR m_Surface;
NameString m_Name; NameString m_Name;
// Ctor Dtor // Ctor Dtor
Surface(Context *context, const Window *window, cstr name); Surface() = default;
Surface(Instance &context, Window const &window);
~Surface(); ~Surface();
// Move // Move
@ -25,4 +28,5 @@ struct Surface
Surface &operator=(Surface &&other) noexcept; Surface &operator=(Surface &&other) noexcept;
DISALLOW_COPY_AND_ASSIGN(Surface); DISALLOW_COPY_AND_ASSIGN(Surface);
}; };
} // namespace aster

View File

@ -1,6 +1,6 @@
/// ============================================= /// =============================================
// Aster: swapchain.h // Aster: swapchain.h
// Copyright (c) 2020-2024 Anish Bhobe // Copyright (c) 2020-2025 Anish Bhobe
// ============================================== // ==============================================
#pragma once #pragma once
@ -11,6 +11,8 @@
#include <EASTL/fixed_vector.h> #include <EASTL/fixed_vector.h>
namespace aster
{
struct PhysicalDevice; struct PhysicalDevice;
struct Surface; struct Surface;
struct Device; struct Device;
@ -19,9 +21,8 @@ struct Swapchain final
{ {
using FnResizeCallback = eastl::function<void(vk::Extent2D)>; using FnResizeCallback = eastl::function<void(vk::Extent2D)>;
const Device *m_Device; Device const *m_Device;
vk::SwapchainKHR m_Swapchain; vk::SwapchainKHR m_Swapchain;
NameString m_Name;
vk::Extent2D m_Extent; vk::Extent2D m_Extent;
vk::Format m_Format; vk::Format m_Format;
eastl::fixed_vector<vk::Image, 4> m_Images; eastl::fixed_vector<vk::Image, 4> m_Images;
@ -29,11 +30,12 @@ struct Swapchain final
eastl::vector<FnResizeCallback> m_ResizeCallbacks; eastl::vector<FnResizeCallback> m_ResizeCallbacks;
void Create(const Surface *window, Size2D size); void Create(Surface const &surface, Size2D size);
void RegisterResizeCallback(FnResizeCallback &&callback); void RegisterResizeCallback(FnResizeCallback &&callback);
// Ctor/Dtor // Ctor/Dtor
Swapchain(const Surface *window, const Device *device, Size2D size, NameString &&name); Swapchain() = default;
Swapchain(Surface const &surface, Device const &device, Size2D size);
~Swapchain(); ~Swapchain();
// Move // Move
@ -45,3 +47,4 @@ struct Swapchain final
private: private:
void Cleanup(); void Cleanup();
}; };
} // namespace aster

View File

@ -1,36 +1,31 @@
// ============================================= // =============================================
// Aster: type_traits.h // Aster: type_traits.h
// Copyright (c) 2020-2024 Anish Bhobe // Copyright (c) 2020-2025 Anish Bhobe
// ============================================= // =============================================
#pragma once #pragma once
#include "constants.h"
namespace aster
{
struct Device; struct Device;
struct Image;
namespace concepts namespace concepts
{ {
template <typename T> template <typename T>
concept DeviceDestructible = requires(T a, Device *p) { concept Deref = requires(T a) {
{ a.Destroy(p) } -> std::convertible_to<void>; { *a };
}; };
template <typename T> template <typename TRef, typename TVal>
concept Committable = requires(T a, bool v) { concept DerefTo = requires(TRef a) {
{ a.IsCommitted() } -> std::convertible_to<bool>; { *a } -> std::convertible_to<TVal>;
{ a.SetCommitted(v) } -> std::convertible_to<void>;
}; };
template <typename T> template <Deref T>
constexpr bool GpuResource = false; using DerefType = std::remove_cvref_t<decltype(*std::declval<T>())>;
template <typename T> } // namespace concepts
concept RenderResource = GpuResource<T> and std::is_default_constructible_v<T> and std::is_trivially_copyable_v<T> and } // namespace aster
DeviceDestructible<T> and Committable<T>;
template <typename T>
constexpr bool IsHandle = false;
template <typename THandle>
concept HandleType = IsHandle<THandle> and RenderResource<typename THandle::Type>;
} // namespace concepts

View File

@ -1,6 +1,6 @@
// ============================================= // =============================================
// Aster: window.h // Aster: window.h
// Copyright (c) 2020-2024 Anish Bhobe // Copyright (c) 2020-2025 Anish Bhobe
// ============================================= // =============================================
#pragma once #pragma once
@ -12,6 +12,8 @@
#include <EASTL/fixed_string.h> #include <EASTL/fixed_string.h>
#include <atomic> #include <atomic>
namespace aster
{
struct Window final struct Window final
{ {
// fields // fields
@ -20,6 +22,8 @@ struct Window final
static std::atomic_uint64_t m_WindowCount; static std::atomic_uint64_t m_WindowCount;
static std::atomic_bool m_IsGlfwInit; static std::atomic_bool m_IsGlfwInit;
static void SetupLibrary();
static cstr *GetInstanceExtensions(u32 *extensionCount);
// Methods // Methods
[[nodiscard]] bool [[nodiscard]] bool
@ -30,7 +34,7 @@ struct Window final
} }
void RequestExit() const noexcept; void RequestExit() const noexcept;
void SetWindowSize(const vk::Extent2D &extent) const noexcept; void SetWindowSize(vk::Extent2D const &extent) const noexcept;
void SetWindowSize(u32 width, u32 height) const noexcept; void SetWindowSize(u32 width, u32 height) const noexcept;
/// Actual size of the framebuffer being used for the window render. /// Actual size of the framebuffer being used for the window render.
[[nodiscard]] Size2D GetSize() const; [[nodiscard]] Size2D GetSize() const;
@ -45,3 +49,4 @@ struct Window final
DISALLOW_COPY_AND_ASSIGN(Window); DISALLOW_COPY_AND_ASSIGN(Window);
}; };
} // namespace aster

View File

@ -0,0 +1,10 @@
// =============================================
// Aster: import_types.h
// Copyright (c) 2020-2024 Anish Bhobe
// =============================================
#pragma once
#include "aster/aster.h"
using namespace aster::types;

View File

@ -4,7 +4,7 @@ cmake_minimum_required(VERSION 3.13)
target_sources(aster_core target_sources(aster_core
INTERFACE INTERFACE
"manager.h" "rendering_device.h"
"buffer_manager.h" "resource.h"
"image_manager.h" "context.h"
"render_resource_manager.h") "commit_manager.h")

View File

@ -1,24 +0,0 @@
// =============================================
// Aster: buffer_manager.h
// Copyright (c) 2020-2024 Anish Bhobe
// =============================================
#pragma once
#include "aster/aster.h"
#include "aster/core/buffer.h"
#include "manager.h"
namespace systems
{
using BufferHandle = Handle<Buffer>;
class BufferManager final : public Manager<Buffer>
{
public:
BufferManager(const Device *device, const u32 maxCount, const u8 binding);
[[nodiscard]] Handle CreateStorageBuffer(usize size, cstr name = nullptr);
[[nodiscard]] Handle CreateUniformBuffer(usize size, cstr name = nullptr);
};
} // namespace systems

View File

@ -0,0 +1,375 @@
// =============================================
// Aster: render_resource_manager.h
// Copyright (c) 2020-2025 Anish Bhobe
// =============================================
#pragma once
#include "aster/aster.h"
#include "aster/util/freelist.h"
#include "EASTL/deque.h"
#include "EASTL/intrusive_hash_map.h"
#include "EASTL/vector.h"
#include "aster/core/buffer.h"
#include "aster/core/image_view.h"
#include "aster/core/sampler.h"
#include "resource.h"
namespace aster
{
class RenderingDevice;
class CommitManager
{
template <typename T>
struct HandleMapper
{
using Type = T;
using Handle = Ref<Type>;
using Resource = ResId<Type>;
struct Entry : eastl::intrusive_hash_node_key<Handle>
{
std::atomic<u32> m_CommitCount;
void
AddRef()
{
auto const rc = ++m_CommitCount;
assert(rc > 0);
}
void
Release()
{
auto const rc = --m_CommitCount;
assert(rc < MaxValue<u32>);
}
u32
IsReferenced() const
{
return m_CommitCount;
}
bool
operator==(Entry const &other) const
{
return this->mKey == other.mKey;
}
Entry *
Next()
{
return reinterpret_cast<Entry *>(this->mpNext);
}
void
SetNext(Entry &entry)
{
this->mpNext = &entry;
}
struct Hash
{
usize
operator()(Handle const &e)
{
return eastl::hash<Type *>()(e.get());
}
};
};
eastl::vector<Entry> m_Data;
FreeList<Entry> m_FreeList;
eastl::intrusive_hash_map<typename Entry::key_type, Entry, 31, typename Entry::Hash> m_InUse;
std::array<FreeList<Entry>, 4> m_ToDelete;
u8 m_ToDeleteIndex = 0;
explicit HandleMapper(u32 const maxCount)
: m_Data{maxCount}
{
// Setup freelist
for (auto it = m_Data.rbegin(); it != m_Data.rend(); ++it)
{
m_FreeList.Push(*it);
}
}
~HandleMapper()
{
for (auto &toDelete : m_ToDelete)
{
ClearEntries(toDelete);
}
}
PIN_MEMORY(HandleMapper);
/// Returns a commit, and a bool signifying if it is a new commit.
std::tuple<Resource, bool>
Create(Handle const &object)
{
// Get-from freelist
assert(!m_FreeList.Empty());
auto it = m_InUse.find(object);
if (it != m_InUse.end())
{
it->AddRef();
auto i = GetIndex(*it);
return {Resource{i}, false};
}
Entry &data = m_FreeList.Pop();
data.mKey = object;
data.m_CommitCount = 0;
m_InUse.insert(data);
auto i = GetIndex(data);
return {Resource{i}, true};
}
Handle
GetHandle(Resource const &res)
{
return m_Data[res.m_Index].mKey;
}
void
AddRef(Resource const &commit)
{
m_Data.at(commit.m_Index).AddRef();
}
void
Release(Resource const &commit)
{
auto &entry = m_Data.at(commit.m_Index);
entry.Release();
if (!entry.IsReferenced())
{
QueueDelete(entry);
}
}
/**
* Sweeps through the delete queue.
* All freed items are cleared. (With a 3 frame delay)
*/
void
Update()
{
m_ToDeleteIndex = (m_ToDeleteIndex + 1) % m_ToDelete.size();
auto &list = m_ToDelete[m_ToDeleteIndex];
ClearEntries(list);
}
private:
u32
GetIndex(Entry const &entry)
{
return static_cast<u32>(&entry - m_Data.begin());
}
void
QueueDelete(Entry &entry)
{
m_InUse.remove(entry);
m_ToDelete[m_ToDeleteIndex].Push(entry);
}
void
ClearEntries(FreeList<Entry> &entries)
{
while (!entries.Empty())
{
Entry &entry = entries.Pop();
entry.mKey.reset();
entry.m_CommitCount = 0;
}
}
};
union WriteInfo {
vk::DescriptorBufferInfo uBufferInfo;
vk::DescriptorImageInfo uImageInfo;
vk::BufferView uBufferView;
explicit WriteInfo(vk::DescriptorBufferInfo const &info);
explicit WriteInfo(vk::DescriptorImageInfo const &info);
explicit WriteInfo(vk::BufferView const &info);
};
using WriteCommand = vk::WriteDescriptorSet;
// using WriteOwner = std::variant<Handle<Buffer>, Handle<Image>>;
public:
RenderingDevice const *m_Device;
CommitManager(RenderingDevice const *device, u32 maxBuffers, u32 maxImages, u32 maxStorageImages,
Ref<Sampler> defaultSampler);
~CommitManager();
PIN_MEMORY(CommitManager);
// Commit Buffer
private:
ResId<Buffer> CommitBuffer(Ref<Buffer> const &buffer);
public:
// Commit Storage Images
ResId<StorageImageView>
CommitStorageImage(concepts::ViewRefTo<StorageImage> auto const &image)
{
return CommitStorageImage(CastView<StorageImageView>(image));
}
ResId<StorageImageView> CommitStorageImage(Ref<StorageImageView> const &image);
// Sampled Images
ResId<TextureView>
CommitTexture(concepts::ViewRefTo<Texture> auto const &image, Ref<Sampler> const &sampler)
{
return CommitTexture(CastView<TextureView>(image), sampler);
}
ResId<TextureView>
CommitTexture(concepts::ViewRefTo<Texture> auto const &image)
{
return CommitTexture(CastView<TextureView>(image));
}
ResId<TextureView> CommitTexture(Ref<TextureView> const &handle);
ResId<TextureView> CommitTexture(Ref<TextureView> const &image, Ref<Sampler> const &sampler);
void Update();
Ref<Buffer>
FetchHandle(ResId<Buffer> const &id)
{
return m_Buffers.GetHandle(id);
}
Ref<TextureView>
FetchHandle(ResId<TextureView> const &id)
{
return m_Images.GetHandle(id);
}
Ref<StorageImageView>
FetchHandle(ResId<StorageImageView> const &id)
{
return m_StorageImages.GetHandle(id);
}
[[nodiscard]] vk::DescriptorSetLayout const &
GetDescriptorSetLayout() const
{
return m_SetLayout;
}
[[nodiscard]] vk::DescriptorSet const &
GetDescriptorSet() const
{
return m_DescriptorSet;
}
static CommitManager &
Instance()
{
assert(m_Instance);
return *m_Instance;
}
static bool
IsInit()
{
return static_cast<bool>(m_Instance);
}
private:
vk::DescriptorPool m_DescriptorPool;
vk::DescriptorSetLayout m_SetLayout;
vk::DescriptorSet m_DescriptorSet;
constexpr static u8 BUFFER_BINDING_INDEX = 0x0;
constexpr static u8 IMAGE_BINDING_INDEX = 0x1;
constexpr static u8 STORAGE_IMAGE_BINDING_INDEX = 0x2;
HandleMapper<Buffer> m_Buffers;
HandleMapper<TextureView> m_Images;
HandleMapper<StorageImageView> m_StorageImages;
Ref<Sampler> m_DefaultSampler;
eastl::vector<vk::WriteDescriptorSet> m_Writes;
eastl::deque<WriteInfo> m_WriteInfos;
// eastl::vector<WriteOwner> m_WriteOwner;
static CommitManager *m_Instance;
friend ResId<Buffer>;
friend ResId<TextureView>;
friend ResId<StorageImageView>;
void
AddRef(ResId<Buffer> const &handle)
{
m_Buffers.AddRef(handle);
}
void
AddRef(ResId<TextureView> const &handle)
{
m_Images.AddRef(handle);
}
void
AddRef(ResId<StorageImageView> const &handle)
{
m_StorageImages.AddRef(handle);
}
void
Release(ResId<Buffer> const &handle)
{
m_Buffers.Release(handle);
}
void
Release(ResId<TextureView> const &handle)
{
m_Images.Release(handle);
}
void
Release(ResId<StorageImageView> const &handle)
{
m_StorageImages.Release(handle);
}
};
template <typename T>
void
ResId<T>::AddRef() const
{
if (m_Index != INVALID)
CommitManager::Instance().AddRef(*this);
}
template <typename T>
void
ResId<T>::Release() const
{
if (m_Index != INVALID)
CommitManager::Instance().Release(*this);
}
} // namespace systems

View File

@ -0,0 +1,471 @@
// =============================================
// Aster: context_pool.h
// Copyright (c) 2020-2025 Anish Bhobe
// =============================================
#pragma once
#include "EASTL/span.h"
#include "context.h"
#include <aster/aster.h>
#include <aster/core/buffer.h>
#include <aster/core/image.h>
#include <aster/core/image_view.h>
#include <aster/core/physical_device.h>
#include <aster/core/pipeline.h>
#include <EASTL/intrusive_list.h>
#include <EASTL/optional.h>
#include <EASTL/vector.h>
#include <foonathan/memory/memory_pool.hpp>
#include <foonathan/memory/namespace_alias.hpp>
namespace aster
{
class RenderingDevice;
struct Frame;
namespace _internal
{
class ComputeContextPool;
class GraphicsContextPool;
class TransferContextPool;
class ContextPool;
} // namespace _internal
#define DEPRECATE_RAW_CALLS
class Context
{
protected:
_internal::ContextPool *m_Pool;
vk::CommandBuffer m_Cmd;
friend RenderingDevice;
friend _internal::ContextPool;
explicit Context(_internal::ContextPool &pool, vk::CommandBuffer const cmd)
: m_Pool{&pool}
, m_Cmd{cmd}
{
}
/// Keep the resource alive while the command buffers are acting.
void KeepAlive(Ref<Buffer> const &buffer);
/// Keep the resource alive while the command buffers are acting.
void KeepAlive(Ref<Image> const &image);
/// Keep the resource alive while the command buffers are acting.
void KeepAlive(Ref<ImageView> const &view);
public:
DEPRECATE_RAW_CALLS void Dependency(vk::DependencyInfo const &dependencyInfo);
void Begin();
void End();
void BeginDebugRegion(cstr name, vec4 color = {});
void EndDebugRegion();
};
// Inline the no-op if not debug.
#if defined(ASTER_NDEBUG)
inline void
Context::BeginDebugRegion(cstr name, vec4 color)
{
}
inline void
Context::EndDebugRegion()
{
}
#endif
class TransferContext : public Context
{
protected:
friend RenderingDevice;
friend _internal::TransferContextPool;
explicit TransferContext(_internal::ContextPool &pool, vk::CommandBuffer const cmd)
: Context{pool, cmd}
{
}
void UploadBuffer(Ref<Buffer> const &buffer, usize size, void const *data);
public:
void UploadTexture(Ref<Image> const &image, eastl::span<u8> const &data);
void
UploadBuffer(Ref<Buffer> const &buffer, std::ranges::range auto const &data)
{
auto const span = eastl::span{data.begin(), data.end()};
UploadBuffer(buffer, span.size_bytes(), span.data());
}
DEPRECATE_RAW_CALLS void Blit(vk::BlitImageInfo2 const &mipBlitInfo);
TransferContext(TransferContext &&other) noexcept;
TransferContext &operator=(TransferContext &&other) noexcept;
~TransferContext() = default;
DISALLOW_COPY_AND_ASSIGN(TransferContext);
};
class ComputeContext : public TransferContext
{
protected:
friend RenderingDevice;
friend _internal::ComputeContextPool;
Pipeline const *m_PipelineInUse;
explicit ComputeContext(_internal::ContextPool &pool, vk::CommandBuffer const cmd)
: TransferContext{pool, cmd}
, m_PipelineInUse{nullptr}
{
}
void PushConstantBlock(usize offset, usize size, void const *data);
void Dispatch(Pipeline const &pipeline, u32 x, u32 y, u32 z, usize size, void *data);
public:
void BindPipeline(Pipeline const &pipeline);
void
PushConstantBlock(auto const &block)
{
if constexpr (sizeof block > 128)
WARN("Vulkan only guarantees 128 bytes of Push Constants. Size of PCB is {}", sizeof block);
PushConstantBlock(0, sizeof block, &block);
}
void
PushConstantBlock(usize const offset, auto const &block)
{
if (offset + sizeof block > 128)
WARN("Vulkan only guarantees 128 bytes of Push Constants. Size of PCB is {}, at offset {}", sizeof block,
offset);
PushConstantBlock(offset, sizeof block, &block);
}
void
Dispatch(Pipeline const &pipeline, u32 const x, u32 const y, u32 const z, auto &pushConstantBlock)
{
if constexpr (sizeof pushConstantBlock > 128)
WARN("Vulkan only guarantees 128 bytes of Push Constants. Size of PCB is {}", sizeof pushConstantBlock);
Dispatch(pipeline, x, y, z, sizeof pushConstantBlock, &pushConstantBlock);
}
};
class GraphicsContext : public ComputeContext
{
protected:
friend RenderingDevice;
friend _internal::GraphicsContextPool;
explicit GraphicsContext(_internal::ContextPool &pool, vk::CommandBuffer const cmd)
: ComputeContext{pool, cmd}
{
}
public:
DEPRECATE_RAW_CALLS void SetViewport(vk::Viewport const &viewport);
void BindVertexBuffer(Ref<VertexBuffer> const &vertexBuffer);
void BindIndexBuffer(Ref<IndexBuffer> const &indexBuffer);
void Draw(usize vertexCount);
void DrawIndexed(usize indexCount);
void DrawIndexed(usize indexCount, usize firstIndex, usize firstVertex);
DEPRECATE_RAW_CALLS void BeginRendering(vk::RenderingInfo const &renderingInfo);
void EndRendering();
DEPRECATE_RAW_CALLS vk::CommandBuffer
GetCommandBuffer() const
{
return m_Cmd;
}
};
namespace _internal
{
class ContextPool
{
protected:
RenderingDevice *m_Device;
vk::CommandPool m_Pool;
eastl::vector<vk::CommandBuffer> m_CommandBuffers;
u32 m_BuffersAllocated;
public:
u16 m_ExtraData;
enum class ManagedBy : u8
{
eFrame,
eDevice,
} m_ManagedBy;
protected:
eastl::vector<Ref<Buffer>> m_OwnedBuffers;
eastl::vector<Ref<Image>> m_OwnedImages;
eastl::vector<Ref<ImageView>> m_OwnedImageViews;
vk::CommandBuffer AllocateCommandBuffer();
public:
[[nodiscard]] RenderingDevice &
GetDevice() const
{
assert(m_Device);
return *m_Device;
}
eastl::function<void(ContextPool &)> m_ResetCallback;
/// Keep the resource alive while the command buffers are acting.
void KeepAlive(Ref<Buffer> const &buffer);
/// Keep the resource alive while the command buffers are acting.
void KeepAlive(Ref<Image> const &image);
/// Keep the resource alive while the command buffers are acting.
void KeepAlive(Ref<ImageView> const &view);
Context CreateContext();
void Reset();
ContextPool() = default;
ContextPool(RenderingDevice &device, u32 queueFamilyIndex, ManagedBy managedBy);
ContextPool(ContextPool &&other) noexcept;
ContextPool &operator=(ContextPool &&other) noexcept;
bool
operator==(ContextPool const &other) const
{
return m_Pool == other.m_Pool;
}
~ContextPool();
DISALLOW_COPY_AND_ASSIGN(ContextPool);
};
class TransferContextPool : public ContextPool
{
public:
TransferContext CreateTransferContext();
TransferContextPool() = default;
TransferContextPool(RenderingDevice &device, u32 const queueFamilyIndex, ManagedBy const managedBy)
: ContextPool{device, queueFamilyIndex, managedBy}
{
}
TransferContextPool(TransferContextPool &&other) noexcept = default;
TransferContextPool &operator=(TransferContextPool &&other) noexcept = default;
~TransferContextPool() = default;
DISALLOW_COPY_AND_ASSIGN(TransferContextPool);
};
class ComputeContextPool : public TransferContextPool
{
public:
ComputeContext CreateComputeContext();
ComputeContextPool() = default;
ComputeContextPool(RenderingDevice &device, u32 const queueFamilyIndex, ManagedBy const managedBy)
: TransferContextPool{device, queueFamilyIndex, managedBy}
{
}
ComputeContextPool(ComputeContextPool &&other) noexcept = default;
ComputeContextPool &operator=(ComputeContextPool &&other) noexcept = default;
~ComputeContextPool() = default;
DISALLOW_COPY_AND_ASSIGN(ComputeContextPool);
};
class GraphicsContextPool : public ComputeContextPool
{
public:
GraphicsContext CreateGraphicsContext();
GraphicsContextPool() = default;
GraphicsContextPool(RenderingDevice &device, u32 const queueFamilyIndex, ManagedBy const managedBy)
: ComputeContextPool{device, queueFamilyIndex, managedBy}
{
}
GraphicsContextPool(GraphicsContextPool &&other) noexcept = default;
GraphicsContextPool &operator=(GraphicsContextPool &&other) noexcept = default;
~GraphicsContextPool() = default;
DISALLOW_COPY_AND_ASSIGN(GraphicsContextPool);
};
template <std::derived_from<ContextPool> TContextPool>
class OrderlessContextPool
{
using ContextPoolType = TContextPool;
struct ContextListEntry : eastl::intrusive_list_node
{
ContextPoolType m_Pool;
bool
Contains(ContextPool const &other) const
{
return m_Pool == other;
}
};
using ContextListType = eastl::intrusive_list<ContextListEntry>;
RenderingDevice *m_Device;
memory::memory_pool<> m_ContextPoolEntryMemory;
ContextListType m_FreeContextPools;
ContextListType m_UsedContextPools;
u32 m_QueueFamilyIndex;
constexpr static usize ENTRY_SIZE = sizeof(ContextListEntry);
constexpr static usize ENTRIES_PER_BLOCK = 5;
constexpr static usize BLOCK_SIZE = ENTRIES_PER_BLOCK * ENTRY_SIZE;
public:
OrderlessContextPool()
: m_Device{nullptr}
, m_ContextPoolEntryMemory{ENTRY_SIZE, BLOCK_SIZE}
, m_QueueFamilyIndex{0}
{
}
void
Init(RenderingDevice &device, u32 const queueFamilyIndex)
{
m_Device = &device;
m_QueueFamilyIndex = queueFamilyIndex;
}
TransferContext
CreateTransferContext()
requires std::derived_from<TContextPool, TransferContextPool>
{
if (!m_FreeContextPools.empty())
{
ContextListEntry &entry = m_FreeContextPools.back();
m_FreeContextPools.pop_back();
m_UsedContextPools.push_back(entry);
return entry.m_Pool.CreateTransferContext();
}
ContextListEntry &entry = *static_cast<ContextListEntry *>(m_ContextPoolEntryMemory.allocate_node());
auto pool = ContextPoolType{*m_Device, m_QueueFamilyIndex, ContextPool::ManagedBy::eDevice};
pool.m_ResetCallback = [this](ContextPool &resetPool) { this->ReleasePool(resetPool); };
new (&entry) ContextListEntry{
.m_Pool = eastl::move(pool),
};
m_UsedContextPools.push_back(entry);
return entry.m_Pool.CreateTransferContext();
}
ComputeContext
CreateComputeContext()
requires std::derived_from<TContextPool, ComputeContextPool>
{
if (!m_FreeContextPools.empty())
{
ContextListEntry &entry = m_FreeContextPools.back();
m_FreeContextPools.pop_back();
m_UsedContextPools.push_back(entry);
return entry.m_Pool.CreateComputeContext();
}
ContextListEntry &entry = *static_cast<ContextListEntry *>(m_ContextPoolEntryMemory.allocate_node());
auto pool = ContextPoolType{*m_Device, m_QueueFamilyIndex, ContextPool::ManagedBy::eDevice};
pool.m_ResetCallback = [this](ContextPool &resetPool) { this->ReleasePool(resetPool); };
new (&entry) ContextListEntry{
.m_Pool = eastl::move(pool),
};
m_UsedContextPools.push_back(entry);
return entry.m_Pool.CreateComputeContext();
}
void
ReleasePool(ContextPool &pool)
{
auto const found = eastl::find_if(m_UsedContextPools.begin(), m_UsedContextPools.end(),
[&pool](ContextListEntry const &v) { return v.Contains(pool); });
auto &v = *found;
ContextListType::remove(v);
pool.Reset();
m_FreeContextPools.push_back(v);
}
OrderlessContextPool(OrderlessContextPool &&other) noexcept
: m_Device{other.m_Device}
, m_ContextPoolEntryMemory{std::move(other.m_ContextPoolEntryMemory)}
, m_FreeContextPools{other.m_FreeContextPools}
, m_UsedContextPools{other.m_UsedContextPools}
, m_QueueFamilyIndex{other.m_QueueFamilyIndex}
{
other.m_FreeContextPools.clear();
other.m_UsedContextPools.clear();
}
OrderlessContextPool &
operator=(OrderlessContextPool &&other) noexcept
{
if (this == &other)
return *this;
m_Device = other.m_Device;
m_ContextPoolEntryMemory = std::move(other.m_ContextPoolEntryMemory);
m_FreeContextPools = other.m_FreeContextPools;
other.m_FreeContextPools.clear();
m_UsedContextPools = other.m_UsedContextPools;
other.m_UsedContextPools.clear();
m_QueueFamilyIndex = other.m_QueueFamilyIndex;
return *this;
}
~OrderlessContextPool()
{
for (auto &entry : m_FreeContextPools)
{
entry.m_Pool.~ContextPoolType();
}
for (auto &entry : m_UsedContextPools)
{
entry.m_Pool.~ContextPoolType();
}
// The allocations will 'wink' away.
}
DISALLOW_COPY_AND_ASSIGN(OrderlessContextPool);
};
using OrderlessTransferContextPool = OrderlessContextPool<TransferContextPool>;
using OrderlessComputeContextPool = OrderlessContextPool<ComputeContextPool>;
} // namespace _internal
} // namespace systems

View File

@ -1,60 +0,0 @@
// =============================================
// Aster: image_manager.h
// Copyright (c) 2020-2024 Anish Bhobe
// =============================================
#pragma once
#include "aster/aster.h"
#include "aster/core/image.h"
#include "manager.h"
namespace systems
{
struct Texture2DCreateInfo
{
vk::Format m_Format = vk::Format::eUndefined;
vk::Extent2D m_Extent = {};
cstr m_Name = nullptr;
bool m_IsSampled = true;
bool m_IsMipMapped = false;
bool m_IsStorage = false;
};
struct TextureCubeCreateInfo
{
vk::Format m_Format = vk::Format::eUndefined;
u32 m_Side = 0;
cstr m_Name = nullptr;
bool m_IsSampled = true;
bool m_IsMipMapped = false;
bool m_IsStorage = false;
};
struct AttachmentCreateInfo
{
vk::Format m_Format = vk::Format::eUndefined;
vk::Extent2D m_Extent = {};
cstr m_Name = nullptr;
};
struct DepthStencilImageCreateInfo
{
vk::Extent2D m_Extent = {};
cstr m_Name = nullptr;
};
using ImageHandle = Handle<Image>;
class ImageManager final : public Manager<Image>
{
public:
ImageManager(const Device *device, const u32 maxCount, const u8 binding);
[[nodiscard]] Handle CreateTexture2D(const Texture2DCreateInfo &createInfo);
[[nodiscard]] Handle CreateTextureCube(const TextureCubeCreateInfo &createInfo);
[[nodiscard]] Handle CreateAttachment(const AttachmentCreateInfo &createInfo);
[[nodiscard]] Handle CreateDepthStencilImage(const DepthStencilImageCreateInfo &createInfo);
};
} // namespace systems

View File

@ -1,361 +0,0 @@
// =============================================
// Aster: manager.h
// Copyright (c) 2020-2024 Anish Bhobe
// =============================================
#pragma once
#include "aster/aster.h"
#include "aster/core/type_traits.h"
struct Device;
template <concepts::RenderResource T>
class Handle;
template <concepts::RenderResource T>
class Manager
{
friend Handle<T>;
public:
using Type = T;
using Handle = Handle<Type>;
static_assert(sizeof(Handle) == sizeof(u32));
constexpr static u32 MAX_HANDLES = Handle::INDEX_MASK + 1;
/**
* Constructor for the Manager class template.
* @param device Device with which resources are created.
* @param maxCount Max number of resources that can be created (maxCount <= Handle::INDEX_MASK)
* @param binding The shader binding at which this manager will bind its resources.
*/
explicit Manager(const Device *device, const u32 maxCount, const u8 binding)
: m_MaxCount{maxCount}
, m_Binding{binding}
, m_Device{device}
{
assert(!m_Instance);
assert(maxCount <= MAX_HANDLES);
m_Data = new Type[m_MaxCount];
m_RefCount = new std::atomic<u32>[m_MaxCount];
for (u32 i = 0; i < m_MaxCount; ++i)
{
*Recast<u32 *>(&m_Data[i]) = (i + 1);
}
m_Instance = this;
}
virtual ~Manager()
{
if (!m_Data)
return;
for (u32 i = 0; i < m_MaxCount; ++i)
{
m_Data[i].Destroy(m_Device);
}
delete[] m_Data;
delete[] m_RefCount;
m_Data = nullptr;
m_RefCount = nullptr;
m_MaxCount = 0;
m_FreeHead = 0;
m_Device = nullptr;
m_Instance = nullptr;
}
/**
* @warning only to be used internally.
* @return The only constructed instance of this manager.
*/
static Manager *
Instance()
{
assert(m_Instance);
return m_Instance;
}
PIN_MEMORY(Manager);
private:
Type *m_Data = nullptr; // Data also keeps the freelist during 'not use'.
std::atomic<u32> *m_RefCount = nullptr; // Associated reference count for each of the instances in Data.
u32 m_MaxCount = 0; // Max number of resources supported.
u32 m_FreeHead = 0;
u8 m_Binding = 0;
static Manager *m_Instance;
/**
* User is expected to type-check.
* @param index Actual index of the resource in the m_Data array. Not type checked.
*/
void
AddRef(const u32 index)
{
assert(index < m_MaxCount);
++m_RefCount[index];
}
/**
* User is expected to type-check.
* @param index Actual index of the resource in the m_Data array. Not type checked.
*/
void
Release(const u32 index)
{
assert(index < m_MaxCount);
const u32 rc = --m_RefCount[index];
assert(rc != MaxValue<u32>);
if (rc == 0)
{
// TODO: Don't destroy here. Separate out to a cleanup routine.
m_Data[index].Destroy(m_Device);
}
}
/**
* User is expected to type-check.
* @param index Actual index of the resource in the m_Data array. Not type checked.
* @return Pointer to the resource at the index.
*/
Type *
Fetch(const u32 index)
{
assert(index < m_MaxCount);
return &m_Data[index];
}
protected:
const Device *m_Device;
/**
* Internal Method to Allocate a resource on the manager.
* @return [Handle, Type*] Where Type* is available to initialize the resource.
*/
[[nodiscard]] std::pair<Handle, Type *>
Alloc()
{
ERROR_IF(m_FreeHead >= m_MaxCount, "Max buffers allocated.") THEN_ABORT(-1);
const auto index = m_FreeHead;
Type *pAlloc = &m_Data[index];
m_FreeHead = *Recast<u32 *>(pAlloc);
return {Handle{index, m_Binding}, pAlloc};
}
};
template <concepts::RenderResource T>
class Ref
{
public:
using Type = T;
using Handle = Handle<Type>;
using Manager = Manager<Type>;
protected:
Handle m_Handle;
Type *m_Pointer = nullptr;
friend Handle;
void
InitPtr()
{
m_Pointer = m_Handle.Fetch();
}
public:
Type *
Get()
{
assert(m_Pointer);
return m_Pointer;
}
const Type *
Get() const
{
assert(m_Pointer);
return m_Pointer;
}
Type *
operator->()
{
return Get();
}
const Type *
operator->() const
{
return Get();
}
Type &
operator*()
{
return *Get();
}
const Type &
operator*() const
{
return Get();
}
// The only constructor requires a valid construction.
explicit Ref(Handle &&handle)
: m_Handle{std::forward<Handle>(handle)}
{
InitPtr();
}
// The only constructor requires a valid construction.
explicit Ref(const Handle &&handle)
: m_Handle{handle}
{
InitPtr();
}
Ref(const Ref &other) = default;
Ref(Ref &&other) noexcept = default;
Ref &operator=(const Ref &other) = default;
Ref &operator=(Ref &&other) noexcept = default;
~Ref() = default;
};
class RawHandle
{
protected:
constexpr static u32 INVALID_HANDLE = MaxValue<u32>;
constexpr static u32 INDEX_MASK = 0x0FFFFFFF;
constexpr static u32 TYPE_MASK = ~INDEX_MASK;
constexpr static u32 TYPE_OFFSET = GetMaskOffset(TYPE_MASK);
u32 m_Internal = INVALID_HANDLE;
RawHandle(const u32 index, const u8 typeId)
: m_Internal{(index & INDEX_MASK) | (typeId & TYPE_MASK)}
{
}
explicit RawHandle(const u32 internal)
: m_Internal{internal}
{
}
public:
[[nodiscard]] bool
IsValid() const
{
return m_Internal != INVALID_HANDLE;
}
[[nodiscard]] u32
GetIndex() const
{
return m_Internal & INDEX_MASK;
}
[[nodiscard]] u32
GetType() const
{
return (m_Internal & TYPE_MASK) >> TYPE_OFFSET;
}
bool
operator==(const RawHandle &other) const
{
return m_Internal == other.m_Internal;
}
};
template <concepts::RenderResource T>
class Handle : public RawHandle
{
public:
using Type = T;
using Manager = Manager<Type>;
protected:
// The only constructor requires a valid construction.
Handle(const u32 index, const u8 typeId)
: RawHandle{index, typeId}
{
AddRef();
}
friend Manager;
friend Ref<T>;
public:
Handle(const Handle &other)
: RawHandle{other}
{
AddRef();
}
Handle(Handle &&other) noexcept
: RawHandle{std::exchange(other.m_Internal, m_Internal)}
{
}
[[nodiscard]] Ref<T>
ToPointer()
{
return Ref{std::move(*this)};
}
[[nodiscard]] Type *
Fetch() const
{
return Manager::Instance()->Fetch(m_Internal);
}
Handle &
operator=(const Handle &other)
{
if (this == &other)
return *this;
m_Internal = other.m_Internal;
AddRef();
return *this;
}
Handle &
operator=(Handle &&other) noexcept
{
if (this == &other)
return *this;
std::swap(m_Internal, other.m_Internal);
return *this;
}
~Handle()
{
if (m_Internal != INVALID_HANDLE)
{
Release();
}
}
protected:
void
AddRef()
{
Manager::Instance()->AddRef(GetIndex());
}
void
Release()
{
Manager::Instance()->Release(GetIndex());
}
};

View File

@ -0,0 +1,75 @@
// =============================================
// Aster: pipeline_helpers.h
// Copyright (c) 2020-2025 Anish Bhobe
// =============================================
#pragma once
#include <aster/aster.h>
#include <EASTL/vector.h>
#include <slang.h>
#include <variant>
namespace aster
{
class RenderingDevice;
struct PipelineCreationError
{
std::variant<std::monostate, vk::Result, SlangResult> m_Data;
std::string What();
i32 Value();
operator bool() const;
PipelineCreationError(vk::Result res);
PipelineCreationError(SlangResult res);
PipelineCreationError();
};
namespace _internal
{
vk::ShaderStageFlagBits SlangToVulkanShaderStage(SlangStage const stage);
struct PipelineLayoutBuilder
{
RenderingDevice *m_Device;
eastl::vector<vk::DescriptorSetLayout> m_DescriptorSetLayouts;
eastl::vector<vk::PushConstantRange> m_PushConstants;
vk::ShaderStageFlags m_Stage;
explicit PipelineLayoutBuilder(RenderingDevice *device, vk::DescriptorSetLayout bindlessLayout = {});
[[nodiscard]] vk::PipelineLayout Build();
[[nodiscard]] vk::DescriptorSetLayout
CreateDescriptorSetLayout(vk::DescriptorSetLayoutCreateInfo const &createInfo) const;
void AddDescriptorSetForParameterBlock(slang::TypeLayoutReflection *layout);
void AddPushConstantRangeForConstantBuffer(slang::TypeLayoutReflection *layout);
void AddSubObjectRange(slang::TypeLayoutReflection *layout, i64 subObjectRangeIndex);
void AddSubObjectRanges(slang::TypeLayoutReflection *layout);
};
struct DescriptorLayoutBuilder
{
PipelineLayoutBuilder *m_PipelineLayoutBuilder;
eastl::vector<vk::DescriptorSetLayoutBinding> m_LayoutBindings;
u32 m_SetIndex;
vk::ShaderStageFlags &Stage() const;
explicit DescriptorLayoutBuilder(PipelineLayoutBuilder *pipelineLayoutBuilder);
void AddGlobalScopeParameters(slang::ProgramLayout *layout);
void AddEntryPointParameters(slang::ProgramLayout *layout);
void AddEntryPointParameters(slang::EntryPointLayout *layout);
void AddAutomaticallyIntroducedUniformBuffer();
void AddRanges(slang::TypeLayoutReflection *layout);
void AddRangesForParamBlockElement(slang::TypeLayoutReflection *layout);
void AddDescriptorRange(slang::TypeLayoutReflection *layout, i64 relativeSetIndex, i64 rangeIndex);
void AddDescriptorRanges(slang::TypeLayoutReflection *layout);
void Build();
};
} // namespace _internal
} // namespace aster

View File

@ -1,157 +0,0 @@
// =============================================
// Aster: render_resource_manager.h
// Copyright (c) 2020-2024 Anish Bhobe
// =============================================
#pragma once
#include "aster/aster.h"
#include "buffer_manager.h"
#include "image_manager.h"
#include "EASTL/deque.h"
#include "EASTL/vector.h"
namespace systems
{
class RenderResourceManager
{
private:
union WriteInfo {
vk::DescriptorBufferInfo uBufferInfo;
vk::DescriptorImageInfo uImageInfo;
vk::BufferView uBufferView;
explicit WriteInfo(const vk::DescriptorBufferInfo &info);
explicit WriteInfo(const vk::DescriptorImageInfo &info);
explicit WriteInfo(const vk::BufferView &info);
};
using WriteCommand = vk::WriteDescriptorSet;
union WriteOwner {
Handle<Buffer> uBufferHandle;
Handle<Image> uImageHandle;
explicit WriteOwner(const Handle<Buffer> &handle);
explicit WriteOwner(const Handle<Image> &handle);
WriteOwner(const WriteOwner &other)
{
switch (uRawHandle.GetType())
{
case BUFFER_BINDING_INDEX:
uBufferHandle = other.uBufferHandle;
break;
case IMAGE_BINDING_INDEX:
uImageHandle = other.uImageHandle;
break;
default:
ERROR("Invalid Handle type.") THEN_ABORT(-1);
}
}
WriteOwner(WriteOwner &&other) noexcept
{
switch (uRawHandle.GetType())
{
case BUFFER_BINDING_INDEX:
uBufferHandle = std::move(other.uBufferHandle);
break;
case IMAGE_BINDING_INDEX:
uImageHandle = std::move(other.uImageHandle);
break;
default:
ERROR("Invalid Handle type.") THEN_ABORT(-1);
}
}
WriteOwner &
operator=(const WriteOwner &other)
{
if (this == &other)
return *this;
switch (uRawHandle.GetType())
{
case BUFFER_BINDING_INDEX:
uBufferHandle = other.uBufferHandle;
break;
case IMAGE_BINDING_INDEX:
uImageHandle = other.uImageHandle;
break;
default:
ERROR("Invalid Handle type.") THEN_ABORT(-1);
}
return *this;
}
WriteOwner &
operator=(WriteOwner &&other) noexcept
{
if (this == &other)
return *this;
switch (uRawHandle.GetType())
{
case BUFFER_BINDING_INDEX:
uBufferHandle = std::move(other.uBufferHandle);
break;
case IMAGE_BINDING_INDEX:
uImageHandle = std::move(other.uImageHandle);
break;
default:
ERROR("Invalid Handle type.") THEN_ABORT(-1);
}
return *this;
}
~WriteOwner()
{
switch (uRawHandle.GetType())
{
case BUFFER_BINDING_INDEX:
uBufferHandle.~Handle();
return;
case IMAGE_BINDING_INDEX:
uImageHandle.~Handle();
return;
default:
ERROR("Invalid Handle type.") THEN_ABORT(-1);
}
}
private:
RawHandle uRawHandle;
};
public:
RenderResourceManager(const Device *device, u32 maxBuffers, u32 maxImages);
void Commit(concepts::HandleType auto &handle);
private:
BufferManager m_BufferManager;
ImageManager m_ImageManager;
vk::DescriptorPool m_DescriptorPool;
vk::DescriptorSetLayout m_SetLayout;
vk::DescriptorSet m_DescriptorSet;
constexpr static u8 BUFFER_BINDING_INDEX = 0;
constexpr static u8 IMAGE_BINDING_INDEX = 1;
eastl::vector<vk::WriteDescriptorSet> m_Writes;
eastl::deque<WriteInfo> m_WriteInfos;
eastl::vector<WriteOwner> m_WriteOwner;
#if !defined(ASTER_NDEBUG)
usize m_CommitedBufferCount = 0;
usize m_CommitedTextureCount = 0;
usize m_CommitedStorageTextureCount = 0;
#endif
};
} // namespace systems

View File

@ -0,0 +1,649 @@
// =============================================
// Aster: rendering_device.h
// Copyright (c) 2020-2025 Anish Bhobe
// =============================================
#pragma once
#include "context.h"
#include "pipeline_helpers.h"
#include "resource.h"
#include "aster/aster.h"
#include "aster/core/buffer.h"
#include "aster/core/device.h"
#include "aster/core/image.h"
#include "aster/core/image_view.h"
#include "aster/core/instance.h"
#include "aster/core/physical_device.h"
#include "aster/core/pipeline.h"
#include "aster/core/sampler.h"
#include "aster/core/size.h"
#include "aster/core/swapchain.h"
#include <EASTL/hash_map.h>
#include <EASTL/optional.h>
#include <EASTL/variant.h>
#include <slang-com-ptr.h>
#include <slang.h>
namespace aster
{
constexpr static u32 MAX_FRAMES_IN_FLIGHT = 3;
struct Window;
} // namespace aster
template <>
struct eastl::hash<vk::SamplerCreateInfo>
{
aster::usize
operator()(vk::SamplerCreateInfo const &createInfo) const noexcept
{
aster::usize hash = aster::HashAny(createInfo.flags);
hash = aster::HashCombine(hash, aster::HashAny(createInfo.magFilter));
hash = aster::HashCombine(hash, aster::HashAny(createInfo.minFilter));
hash = aster::HashCombine(hash, aster::HashAny(createInfo.mipmapMode));
hash = aster::HashCombine(hash, aster::HashAny(createInfo.addressModeU));
hash = aster::HashCombine(hash, aster::HashAny(createInfo.addressModeV));
hash = aster::HashCombine(hash, aster::HashAny(createInfo.addressModeW));
hash = aster::HashCombine(hash, aster::HashAny(static_cast<aster::usize>(createInfo.mipLodBias * 1000))); // Resolution of 10^-3
hash = aster::HashCombine(hash, aster::HashAny(createInfo.anisotropyEnable));
hash = aster::HashCombine(
hash,
aster::HashAny(static_cast<aster::usize>(createInfo.maxAnisotropy * 0x20))); // 32:1 Anisotropy is enough resolution
hash = aster::HashCombine(hash, aster::HashAny(createInfo.compareEnable));
hash = aster::HashCombine(hash, aster::HashAny(createInfo.compareOp));
hash = aster::HashCombine(hash, aster::HashAny(static_cast<aster::usize>(createInfo.minLod * 1000))); // 0.001 resolution is enough.
hash = aster::HashCombine(
hash,
aster::HashAny(static_cast<aster::usize>(createInfo.maxLod * 1000))); // 0.001 resolution is enough. (1 == NO Clamp)
hash = aster::HashCombine(hash, aster::HashAny(createInfo.borderColor));
hash = aster::HashCombine(hash, aster::HashAny(createInfo.unnormalizedCoordinates));
return hash;
}
};
namespace aster
{
// ====================================================================================================
#pragma region Creation Structs
// ====================================================================================================
// ----------------------------------------------------------------------------------------------------
#pragma region Image
// ----------------------------------------------------------------------------------------------------
struct Texture2DCreateInfo
{
vk::Format m_Format = vk::Format::eUndefined;
vk::Extent2D m_Extent = {};
cstr m_Name = nullptr;
bool m_IsSampled = true;
bool m_IsMipMapped = false;
bool m_IsStorage = false;
};
struct TextureCubeCreateInfo
{
vk::Format m_Format = vk::Format::eUndefined;
u32 m_Side = 0;
cstr m_Name = nullptr;
bool m_IsSampled = true;
bool m_IsMipMapped = false;
bool m_IsStorage = false;
};
struct AttachmentCreateInfo
{
vk::Format m_Format = vk::Format::eUndefined;
vk::Extent2D m_Extent = {};
cstr m_Name = nullptr;
};
struct DepthStencilImageCreateInfo
{
vk::Extent2D m_Extent = {};
cstr m_Name = nullptr;
};
#pragma endregion
// ----------------------------------------------------------------------------------------------------
#pragma region View
// ----------------------------------------------------------------------------------------------------
template <concepts::AnyImage TImage>
struct ViewCreateInfo
{
using ImageType = TImage;
Ref<ImageType> m_Image;
cstr m_Name;
vk::ImageViewType m_ViewType = vk::ImageViewType::e2D;
vk::ComponentMapping m_Components = {};
vk::ImageAspectFlags m_AspectMask = {};
eastl::optional<u8> m_MipLevelCount = eastl::nullopt;
eastl::optional<u8> m_LayerCount = eastl::nullopt;
u8 m_BaseMipLevel = 0;
u8 m_BaseLayer = 0;
[[nodiscard]] u8
GetMipLevelCount() const
{
return m_MipLevelCount.value_or(m_Image->m_MipLevels - m_BaseMipLevel);
}
[[nodiscard]] u8
GetLayerCount() const
{
return m_LayerCount.value_or(m_Image->m_LayerCount - m_BaseLayer);
}
explicit
operator vk::ImageViewCreateInfo() const
{
return {
.image = m_Image->m_Image,
.viewType = m_ViewType,
.format = m_Image->m_Format,
.components = m_Components,
.subresourceRange =
{
.aspectMask = m_AspectMask,
.baseMipLevel = m_BaseMipLevel,
.levelCount = GetMipLevelCount(),
.baseArrayLayer = m_BaseLayer,
.layerCount = GetLayerCount(),
},
};
}
explicit
operator ViewCreateInfo<Image>() const
{
return {
.m_Image = CastImage<Image>(m_Image),
.m_Name = m_Name,
.m_ViewType = m_ViewType,
.m_Components = m_Components,
.m_AspectMask = m_AspectMask,
.m_MipLevelCount = m_MipLevelCount,
.m_LayerCount = m_LayerCount,
.m_BaseMipLevel = m_BaseMipLevel,
.m_BaseLayer = m_BaseLayer,
};
}
};
#pragma endregion
// ----------------------------------------------------------------------------------------------------
#pragma region Sampler
// ----------------------------------------------------------------------------------------------------
struct SamplerCreateInfo
{
cstr m_Name = nullptr;
vk::SamplerCreateFlags m_Flags = {};
vk::Filter m_MagFilter = vk::Filter::eLinear;
vk::Filter m_MinFilter = vk::Filter::eLinear;
vk::SamplerMipmapMode m_MipmapMode = vk::SamplerMipmapMode::eLinear;
vk::SamplerAddressMode m_AddressModeU = vk::SamplerAddressMode::eRepeat;
vk::SamplerAddressMode m_AddressModeV = vk::SamplerAddressMode::eRepeat;
vk::SamplerAddressMode m_AddressModeW = vk::SamplerAddressMode::eRepeat;
vk::BorderColor m_BorderColor = vk::BorderColor::eFloatOpaqueBlack;
vk::CompareOp m_CompareOp = vk::CompareOp::eNever;
f32 m_MipLodBias = 0.0f;
f32 m_MaxAnisotropy = 16.0f;
f32 m_MinLod = 0;
f32 m_MaxLod = VK_LOD_CLAMP_NONE;
bool m_AnisotropyEnable = true;
bool m_CompareEnable = false;
bool m_NormalizedCoordinates = true;
explicit
operator vk::SamplerCreateInfo() const
{
return {
.flags = m_Flags,
.magFilter = m_MagFilter,
.minFilter = m_MinFilter,
.mipmapMode = m_MipmapMode,
.addressModeU = m_AddressModeU,
.addressModeV = m_AddressModeV,
.addressModeW = m_AddressModeW,
.mipLodBias = m_MipLodBias,
.anisotropyEnable = m_AnisotropyEnable,
.maxAnisotropy = m_MaxAnisotropy,
.compareEnable = m_CompareEnable,
.compareOp = m_CompareOp,
.minLod = m_MinLod,
.maxLod = m_MaxLod,
.borderColor = m_BorderColor,
.unnormalizedCoordinates = !m_NormalizedCoordinates,
};
}
};
#pragma endregion
// ----------------------------------------------------------------------------------------------------
#pragma region Pipeline
// ----------------------------------------------------------------------------------------------------
struct AttributeInfo
{
u32 m_Location;
u32 m_Offset;
enum class Format
{
eFloat32X4,
eFloat32X3,
eFloat32X2,
eFloat32,
} m_Format;
[[nodiscard]] vk::Format
GetFormat() const
{
switch (m_Format)
{
case Format::eFloat32X4:
return vk::Format::eR32G32B32A32Sfloat;
case Format::eFloat32X3:
return vk::Format::eR32G32B32Sfloat;
case Format::eFloat32X2:
return vk::Format::eR32G32Sfloat;
case Format::eFloat32:
return vk::Format::eR32Sfloat;
}
return vk::Format::eUndefined;
}
};
struct VertexInput
{
eastl::vector<AttributeInfo> m_Attribute;
u32 m_Stride;
bool m_IsPerInstance;
};
enum class ShaderType
{
eInvalid = 0,
eVertex = VK_SHADER_STAGE_VERTEX_BIT,
eTesselationControl = VK_SHADER_STAGE_TESSELLATION_CONTROL_BIT,
eTesselationEvaluation = VK_SHADER_STAGE_TESSELLATION_EVALUATION_BIT,
eGeometry = VK_SHADER_STAGE_GEOMETRY_BIT,
eFragment = VK_SHADER_STAGE_FRAGMENT_BIT,
eCompute = VK_SHADER_STAGE_COMPUTE_BIT,
eTask = VK_SHADER_STAGE_TASK_BIT_EXT,
eMesh = VK_SHADER_STAGE_MESH_BIT_EXT,
eMax,
};
constexpr static u32 ShaderTypeCount = 8;
static_assert(static_cast<u32>(ShaderType::eMax) == 1 + (1 << (ShaderTypeCount - 1)));
struct ShaderInfo
{
std::string_view m_ShaderFile;
eastl::vector<std::string_view> m_EntryPoints;
};
struct GraphicsPipelineCreateInfo
{
enum class DepthTest
{
eEnabled,
eReadOnly,
eDisabled,
};
enum class CompareOp
{
eNever = 0x0,
eLessThan = 0x1,
eEqualTo = 0x2,
eGreaterThan = 0x4,
eLessThanOrEqualTo = eLessThan | eEqualTo,
eGreaterThanOrEqualTo = eGreaterThan | eEqualTo,
eNotEqualTo = eLessThan | eGreaterThan,
eAlways = eLessThan | eEqualTo | eGreaterThan,
};
eastl::fixed_vector<VertexInput, 4, false> m_VertexInputs;
eastl::fixed_vector<ShaderInfo, 4, false> m_Shaders;
DepthTest m_DepthTest = DepthTest::eEnabled;
CompareOp m_DepthOp = CompareOp::eLessThan;
cstr m_Name;
private:
friend RenderingDevice;
[[nodiscard]] vk::PipelineDepthStencilStateCreateInfo GetDepthStencilStateCreateInfo() const;
};
struct ComputePipelineCreateInfo
{
ShaderInfo m_Shader;
cstr m_Name;
};
#pragma endregion
// ----------------------------------------------------------------------------------------------------
#pragma region Device
// ----------------------------------------------------------------------------------------------------
PhysicalDevice DefaultPhysicalDeviceSelector(PhysicalDevices const &physicalDevices);
using PhysicalDeviceSelectorFn = PhysicalDevice (*)(PhysicalDevices const &);
static_assert(std::convertible_to<decltype(DefaultPhysicalDeviceSelector), PhysicalDeviceSelectorFn>);
struct DeviceCreateInfo
{
std::reference_wrapper<Window> m_Window;
Features m_Features;
cstr m_AppName = "Aster App";
Version m_AppVersion = {0, 1, 0};
PhysicalDeviceSelectorFn m_PhysicalDeviceSelector = DefaultPhysicalDeviceSelector;
std::span<u8> m_PipelineCacheData = {};
eastl::vector<cstr> m_ShaderSearchPaths;
bool m_UseBindless = true;
cstr m_Name = "Primary";
};
#pragma endregion
#pragma endregion
namespace _internal
{
class SyncServer;
}
class Receipt
{
void *m_Opaque;
explicit Receipt(void *opaque)
: m_Opaque{opaque}
{
}
friend _internal::SyncServer;
};
struct Frame
{
// Persistent
RenderingDevice *m_Device;
// TODO: ThreadSafe
_internal::GraphicsContextPool m_PrimaryPool;
_internal::TransferContextPool m_AsyncTransferPool;
_internal::ComputeContextPool m_AsyncComputePool;
vk::Fence m_FrameAvailableFence;
vk::Semaphore m_ImageAcquireSem;
vk::Semaphore m_RenderFinishSem;
u32 m_FrameIdx;
// Transient
vk::Image m_SwapchainImage;
vk::ImageView m_SwapchainImageView;
Size2D m_SwapchainSize;
u32 m_ImageIdx;
void Reset(u32 imageIdx, vk::Image swapchainImage, vk::ImageView swapchainImageView, Size2D swapchainSize);
GraphicsContext CreateGraphicsContext();
TransferContext CreateAsyncTransferContext();
ComputeContext CreateAsyncComputeContext();
void WaitUntilReady();
Frame() = default;
Frame(RenderingDevice &device, u32 frameIndex, u32 primaryQueueFamily, u32 asyncTransferQueue,
u32 asyncComputeQueue);
Frame(Frame &&other) noexcept;
Frame &operator=(Frame &&other) noexcept;
DISALLOW_COPY_AND_ASSIGN(Frame);
~Frame() = default;
};
class CommitManager;
class RenderingDevice final
{
public: // TODO: Temp
std::reference_wrapper<Window> m_Window;
Instance m_Instance;
Surface m_Surface;
Device m_Device;
Swapchain m_Swapchain;
std::unique_ptr<CommitManager> m_CommitManager;
// TODO: This is single-threaded.
vk::Queue m_PrimaryQueue;
u32 m_PrimaryQueueFamily;
vk::Queue m_TransferQueue;
u32 m_TransferQueueFamily;
vk::Queue m_ComputeQueue;
u32 m_ComputeQueueFamily;
_internal::OrderlessTransferContextPool m_TransferContextPool;
_internal::OrderlessComputeContextPool m_ComputeContextPool;
std::array<Frame, MAX_FRAMES_IN_FLIGHT> m_Frames;
u32 m_CurrentFrameIdx = 0;
public:
// ====================================================================================================
// Resource Management
// ====================================================================================================
//
// Buffer Management
// ----------------------------------------------------------------------------------------------------
[[nodiscard]] Ref<StorageBuffer> CreateStorageBuffer(usize size, cstr name = nullptr);
[[nodiscard]] Ref<IndexBuffer> CreateIndexBuffer(usize size, cstr name = nullptr);
[[nodiscard]] Ref<UniformBuffer> CreateUniformBuffer(usize size, cstr name = nullptr);
[[nodiscard]] Ref<StagingBuffer> CreateStagingBuffer(usize size, cstr name = nullptr);
[[nodiscard]] Ref<VertexBuffer> CreateVertexBuffer(usize size, cstr name = nullptr);
//
// Image Management
// ----------------------------------------------------------------------------------------------------
template <concepts::ImageInto<Texture> T>
[[nodiscard]] Ref<T>
CreateTexture2D(Texture2DCreateInfo const &createInfo)
{
return CastImage<T>(CreateTexture2D(createInfo));
}
template <concepts::ImageInto<TextureCube> T>
[[nodiscard]] Ref<T>
CreateTextureCube(TextureCubeCreateInfo const &createInfo)
{
return CastImage<T>(CreateTextureCube(createInfo));
}
[[nodiscard]] Ref<Image> CreateTexture2D(Texture2DCreateInfo const &createInfo);
[[nodiscard]] Ref<ImageCube> CreateTextureCube(TextureCubeCreateInfo const &createInfo);
[[nodiscard]] Ref<Image> CreateAttachment(AttachmentCreateInfo const &createInfo);
[[nodiscard]] Ref<Image> CreateDepthStencilImage(DepthStencilImageCreateInfo const &createInfo);
//
// View Management
// ----------------------------------------------------------------------------------------------------
template <concepts::View TImageView>
Ref<TImageView>
CreateView(ViewCreateInfo<typename TImageView::ImageType> const &createInfo)
{
return CastView<TImageView>(CreateView(ViewCreateInfo<Image>(createInfo)));
}
[[nodiscard]] Ref<ImageView> CreateView(ViewCreateInfo<Image> const &createInfo);
//
// Image - View Combined Management
// ----------------------------------------------------------------------------------------------------
template <concepts::ViewTo<Image> T>
[[nodiscard]] Ref<T>
CreateTexture2DWithView(Texture2DCreateInfo const &createInfo)
{
auto handle = CreateTexture2DWithView(createInfo);
return CastView<T>(handle);
}
template <concepts::ViewTo<ImageCube> T>
[[nodiscard]] Ref<T>
CreateTextureCubeWithView(TextureCubeCreateInfo const &createInfo)
{
auto handle = CreateTextureCubeWithView(createInfo);
return CastView<T>(handle);
}
[[nodiscard]] Ref<TextureView> CreateTexture2DWithView(Texture2DCreateInfo const &createInfo);
[[nodiscard]] Ref<ImageCubeView> CreateTextureCubeWithView(TextureCubeCreateInfo const &createInfo);
[[nodiscard]] Ref<ImageView> CreateAttachmentWithView(AttachmentCreateInfo const &createInfo);
[[nodiscard]] Ref<ImageView> CreateDepthStencilImageWithView(DepthStencilImageCreateInfo const &createInfo);
//
// Sampler Management
// ----------------------------------------------------------------------------------------------------
private:
eastl::hash_map<vk::SamplerCreateInfo, WeakRef<Sampler>> m_HashToSamplerIdx;
public:
Ref<Sampler> CreateSampler(SamplerCreateInfo const &createInfo);
//
// Pipeline
// ----------------------------------------------------------------------------------------------------
// TODO: Cache shader modules for reuse. Time to move to `slang`
private:
Slang::ComPtr<slang::IGlobalSession> m_GlobalSlangSession;
Slang::ComPtr<slang::ISession> m_SlangSession;
PipelineCreationError
CreateShaders(eastl::fixed_vector<vk::PipelineShaderStageCreateInfo, ShaderTypeCount, false> &shadersOut,
Slang::ComPtr<slang::IComponentType> &program, std::span<ShaderInfo const> const &shaders);
PipelineCreationError
CreatePipelineLayout(vk::PipelineLayout &pipelineLayout, Slang::ComPtr<slang::IComponentType> const &program);
public:
// Pipelines, unlike the other resources, are not ref-counted.
PipelineCreationError CreateGraphicsPipeline(Pipeline &pipeline, GraphicsPipelineCreateInfo const &createInfo);
PipelineCreationError CreateComputePipeline(Pipeline &pipeline, ComputePipelineCreateInfo const &createInfo);
//
// Frames
// ----------------------------------------------------------------------------------------------------
public:
Frame &GetNextFrame();
Size2D
GetSwapchainSize() const
{
return {m_Swapchain.m_Extent.width, m_Swapchain.m_Extent.height};
}
void
RegisterResizeCallback(Swapchain::FnResizeCallback &&callback)
{
m_Swapchain.RegisterResizeCallback(eastl::forward<Swapchain::FnResizeCallback>(callback));
}
void Present(Frame &frame, GraphicsContext &graphicsContext);
//
// Context
// ----------------------------------------------------------------------------------------------------
friend Context;
friend GraphicsContext;
friend TransferContext;
TransferContext CreateTransferContext();
ComputeContext CreateComputeContext();
Receipt Submit(Context &context);
//
// Sync
// ----------------------------------------------------------------------------------------------------
std::unique_ptr<_internal::SyncServer> m_SyncServer;
void WaitOn(Receipt recpt);
//
// RenderingDevice Methods
// ----------------------------------------------------------------------------------------------------
template <concepts::VkHandle T>
void
SetName(T const &object, cstr name) const
{
m_Device.SetName(object, name);
}
[[nodiscard]] vk::Queue
GetQueue(u32 const familyIndex, u32 const queueIndex) const
{
return m_Device.GetQueue(familyIndex, queueIndex);
}
[[nodiscard]] eastl::vector<u8>
DumpPipelineCache() const
{
return m_Device.DumpPipelineCache();
}
void
WaitIdle() const
{
m_Device.WaitIdle();
}
// Inner
// ----------------------------------------------------------------------------------------------------
[[nodiscard]] Device &
GetInner()
{
return m_Device;
}
[[nodiscard]] vk::Device &
GetHandle()
{
return m_Device.m_Device;
}
// Ctor/Dtor
// ----------------------------------------------------------------------------------------------------
explicit RenderingDevice(DeviceCreateInfo const &createInfo);
~RenderingDevice();
PIN_MEMORY(RenderingDevice);
};
} // namespace systems

View File

@ -0,0 +1,145 @@
// =============================================
// Aster: resource.h
// Copyright (c) 2020-2025 Anish Bhobe
// =============================================
#pragma once
#include "aster/core/buffer.h"
#include "aster/core/image.h"
#include "aster/core/image_view.h"
#include <EASTL/intrusive_ptr.h>
namespace aster
{
// ====================================================================================================
#pragma region Util Methods
// ====================================================================================================
#pragma region Buffer
// ----------------------------------------------------------------------------------------------------
template <std::derived_from<Buffer> TTo, std::derived_from<Buffer> TFrom>
static Ref<TTo>
CastBuffer(Ref<TFrom> const &from)
{
if constexpr (not concepts::BufferInto<TFrom, TTo>)
assert(TTo::FLAGS & from->m_Flags);
return eastl::reinterpret_pointer_cast<TTo>(from);
}
#pragma endregion
#pragma region Image
// ----------------------------------------------------------------------------------------------------
template <std::derived_from<Image> TTo, std::derived_from<Image> TFrom>
static Ref<TTo>
CastImage(Ref<TFrom> const &from)
{
if constexpr (not concepts::ImageInto<TFrom, TTo>)
assert(TTo::FLAGS & from->m_Flags_);
return eastl::reinterpret_pointer_cast<TTo>(from);
}
#pragma endregion
#pragma region View
// ----------------------------------------------------------------------------------------------------
template <concepts::View TTo, std::derived_from<Image> TFrom>
static Ref<TTo>
CastView(Ref<View<TFrom>> const &from)
{
if constexpr (not concepts::ImageInto<TFrom, typename TTo::ImageType>)
assert(TTo::ImageType::FLAGS & from->m_Image->m_Flags_);
return eastl::reinterpret_pointer_cast<TTo>(from);
}
#pragma endregion
#pragma endregion
/**
* ResId manages the lifetime of the committed resource.
* @tparam T Type of the committed resource.
*/
template <typename T>
class ResId
{
using IdType = u32;
public:
constexpr static IdType INVALID = MaxValue<IdType>;
private:
IdType m_Index;
u32 m_Padding = 0; //< Slang DescriptorHandle are a pair of u32. TODO: Use as validation.
explicit ResId(IdType const index)
: m_Index{index}
{
AddRef();
}
friend class CommitManager;
public:
static ResId
Null()
{
return ResId{INVALID};
}
ResId(ResId const &other)
: m_Index{other.m_Index}
{
AddRef();
}
ResId(ResId &&other) noexcept
: m_Index{other.m_Index}
{
AddRef();
}
ResId &
operator=(ResId const &other)
{
if (this == &other)
return *this;
m_Index = other.m_Index;
AddRef();
return *this;
}
ResId &
operator=(ResId &&other) noexcept
{
if (this == &other)
return *this;
m_Index = other.m_Index;
AddRef();
return *this;
}
~ResId()
{
Release();
}
private:
void AddRef() const; ///< Increases the refcount in the CommitManager.
void Release() const; ///< Decreases the refcount in the CommitManager.
};
struct NullId
{
template <typename T>
operator ResId<T>()
{
return ResId<T>::Null();
}
};
} // namespace systems

View File

@ -0,0 +1,79 @@
// =============================================
// Aster: sync_server.h
// Copyright (c) 2020-2025 Anish Bhobe
// =============================================
#include "aster/aster.h"
#include "context.h"
#include <EASTL/deque.h>
#include <EASTL/intrusive_list.h>
namespace aster
{
class Receipt;
class RenderingDevice;
} // namespace systems
namespace aster::_internal
{
struct TimelinePoint
{
u64 m_WaitValue;
u64 m_NextValue;
};
class SyncServer
{
struct Entry : eastl::intrusive_list_node
{
vk::Semaphore m_Semaphore;
TimelinePoint m_CurrentPoint;
ContextPool *m_AttachedPool;
explicit Entry(RenderingDevice &device);
void Destroy(RenderingDevice &device);
void Wait(RenderingDevice &device);
void Next();
void AttachPool(ContextPool *pool);
Entry(Entry &&) = default;
Entry &operator=(Entry &&) = default;
~Entry() = default;
DISALLOW_COPY_AND_ASSIGN(Entry);
};
RenderingDevice *m_Device;
eastl::deque<Entry> m_Allocations;
eastl::intrusive_list<Entry> m_FreeList;
public:
Receipt Allocate();
void Free(Receipt);
void WaitOn(Receipt);
private:
static Entry &GetEntry(Receipt receipt);
// Inner Alloc/Free functions.
Entry &AllocateEntry();
void FreeEntry(Entry &entry);
// Constructor/Destructor
explicit SyncServer(RenderingDevice &device);
public:
~SyncServer();
// Move Constructors.
SyncServer(SyncServer &&other) noexcept;
SyncServer &operator=(SyncServer &&other) noexcept;
friend RenderingDevice;
DISALLOW_COPY_AND_ASSIGN(SyncServer);
};
} // namespace systems::_internal

View File

@ -3,4 +3,7 @@
cmake_minimum_required(VERSION 3.13) cmake_minimum_required(VERSION 3.13)
target_sources(aster_core target_sources(aster_core
INTERFACE "logger.h") INTERFACE
"logger.h"
"freelist.h"
"files.h")

View File

@ -0,0 +1,18 @@
// =============================================
// Aster: files.h
// Copyright (c) 2020-2024 Anish Bhobe
// =============================================
#pragma once
#include "aster/core/constants.h"
#include <EASTL/span.h>
#include <EASTL/vector.h>
namespace aster
{
eastl::vector<u32> ReadFile(std::string_view fileName);
eastl::vector<u8> ReadFileBytes(std::string_view fileName, bool errorOnFail = true);
bool WriteFileBytes(std::string_view fileName, eastl::span<u8> data);
} // namespace aster

View File

@ -0,0 +1,96 @@
// =============================================
// Aster: freelist.h
// Copyright (c) 2020-2025 Anish Bhobe
// =============================================
#pragma once
#include <optional>
struct FreeListNode
{
FreeListNode *m_Next;
};
template <typename T>
concept FreeListCapable = sizeof(T) >= sizeof(FreeListNode);
template <FreeListCapable T>
struct FreeList
{
using Value = T;
using Reference = T &;
using ConstReference = T const &;
using Pointer = T *;
FreeListNode *m_Top;
FreeList()
: m_Top{nullptr}
{
}
FreeList(FreeList &&other) noexcept
: m_Top{Take(other.m_Top)}
{
}
FreeList &
operator=(FreeList &&other) noexcept
{
if (this == &other)
return *this;
m_Top = Take(other.m_Top);
return *this;
}
DISALLOW_COPY_AND_ASSIGN(FreeList);
~FreeList()
{
m_Top = nullptr;
}
[[nodiscard]] bool
Empty() const
{
return !m_Top;
}
[[nodiscard]] Reference
Pop()
{
assert(m_Top);
Reference ref = *reinterpret_cast<Pointer>(m_Top);
m_Top = m_Top->m_Next;
return ref;
}
void
Push(Reference ref)
{
auto next = reinterpret_cast<FreeListNode *>(&ref);
next->m_Next = m_Top;
m_Top = next;
}
[[nodiscard]] ConstReference
Peek() const
{
assert(m_Top);
return *m_Top;
}
[[nodiscard]] Reference
Peek()
{
assert(m_Top);
return *m_Top;
}
void
Clear()
{
m_Top = nullptr;
}
};

View File

@ -9,6 +9,8 @@
#include <debugbreak.h> #include <debugbreak.h>
#include <fmt/core.h> #include <fmt/core.h>
namespace aster
{
struct Logger struct Logger
{ {
enum class LogType : u32 enum class LogType : u32
@ -20,16 +22,16 @@ struct Logger
eVerbose, eVerbose,
}; };
u32 m_MinimumLoggingLevel{Cast<u32>(LogType::eDebug)}; u32 m_MinimumLoggingLevel{static_cast<u32>(LogType::eDebug)};
void void
SetMinimumLoggingLevel(LogType logType) SetMinimumLoggingLevel(LogType logType)
{ {
m_MinimumLoggingLevel = Cast<u32>(logType); m_MinimumLoggingLevel = static_cast<u32>(logType);
} }
template <LogType TLogLevel> template <LogType TLogLevel>
constexpr static const char * constexpr static char const *
ToCstr() ToCstr()
{ {
if constexpr (TLogLevel == LogType::eError) if constexpr (TLogLevel == LogType::eError)
@ -45,7 +47,7 @@ struct Logger
} }
template <LogType TLogLevel> template <LogType TLogLevel>
constexpr static const char * constexpr static char const *
ToColorCstr() ToColorCstr()
{ {
if constexpr (TLogLevel == LogType::eError) if constexpr (TLogLevel == LogType::eError)
@ -62,9 +64,9 @@ struct Logger
template <LogType TLogLevel> template <LogType TLogLevel>
void void
Log(const std::string_view &message, const char *loc, u32 line) const Log(std::string_view const &message, char const *loc, u32 line) const
{ {
if (Cast<u32>(TLogLevel) <= m_MinimumLoggingLevel) if (static_cast<u32>(TLogLevel) <= m_MinimumLoggingLevel)
{ {
fmt::println("{}{} {} {} at {}:{}{}", ToColorCstr<TLogLevel>(), ToCstr<TLogLevel>(), message.data(), fmt::println("{}{} {} {} at {}:{}{}", ToColorCstr<TLogLevel>(), ToCstr<TLogLevel>(), message.data(),
ansi_color::Black, loc, line, ansi_color::Reset); ansi_color::Black, loc, line, ansi_color::Reset);
@ -79,9 +81,9 @@ struct Logger
template <LogType TLogLevel> template <LogType TLogLevel>
void void
LogCond(const char *exprStr, const std::string_view &message, const char *loc, u32 line) const LogCond(char const *exprStr, std::string_view const &message, char const *loc, u32 line) const
{ {
if (Cast<u32>(TLogLevel) <= m_MinimumLoggingLevel) if (static_cast<u32>(TLogLevel) <= m_MinimumLoggingLevel)
{ {
fmt::println("{}{} ({}) {} {} at {}:{}{}", ToColorCstr<TLogLevel>(), ToCstr<TLogLevel>(), exprStr, fmt::println("{}{} ({}) {} {} at {}:{}{}", ToColorCstr<TLogLevel>(), ToCstr<TLogLevel>(), exprStr,
message.data(), ansi_color::Black, loc, line, ansi_color::Reset); message.data(), ansi_color::Black, loc, line, ansi_color::Reset);
@ -94,60 +96,61 @@ struct Logger
#endif #endif
} }
}; };
}
extern Logger g_Logger; extern aster::Logger g_Logger;
#define MIN_LOG_LEVEL(LOG_LVL) g_Logger.SetMinimumLoggingLevel(LOG_LVL) #define MIN_LOG_LEVEL(LOG_LVL) g_Logger.SetMinimumLoggingLevel(LOG_LVL)
#define ERROR(...) g_Logger.Log<Logger::LogType::eError>(fmt::format(__VA_ARGS__), __FILE__, __LINE__) #define ERROR(...) g_Logger.Log<aster::Logger::LogType::eError>(fmt::format(__VA_ARGS__), __FILE__, __LINE__)
#define WARN(...) g_Logger.Log<Logger::LogType::eWarning>(fmt::format(__VA_ARGS__), __FILE__, __LINE__) #define WARN(...) g_Logger.Log<aster::Logger::LogType::eWarning>(fmt::format(__VA_ARGS__), __FILE__, __LINE__)
#define INFO(...) g_Logger.Log<Logger::LogType::eInfo>(fmt::format(__VA_ARGS__), __FILE__, __LINE__) #define INFO(...) g_Logger.Log<aster::Logger::LogType::eInfo>(fmt::format(__VA_ARGS__), __FILE__, __LINE__)
#define ERROR_IF(expr, ...) \ #define ERROR_IF(expr, ...) \
if (Cast<bool>(expr)) [[unlikely]] \ if (static_cast<bool>(expr)) [[unlikely]] \
g_Logger.LogCond<Logger::LogType::eError>(#expr, fmt::format(__VA_ARGS__), __FILE__, __LINE__) g_Logger.LogCond<aster::Logger::LogType::eError>(#expr, fmt::format(__VA_ARGS__), __FILE__, __LINE__)
#define WARN_IF(expr, ...) \ #define WARN_IF(expr, ...) \
if (Cast<bool>(expr)) [[unlikely]] \ if (static_cast<bool>(expr)) [[unlikely]] \
g_Logger.LogCond<Logger::LogType::eWarning>(#expr, fmt::format(__VA_ARGS__), __FILE__, __LINE__) g_Logger.LogCond<aster::Logger::LogType::eWarning>(#expr, fmt::format(__VA_ARGS__), __FILE__, __LINE__)
#define INFO_IF(expr, ...) \ #define INFO_IF(expr, ...) \
if (Cast<bool>(expr)) \ if (static_cast<bool>(expr)) \
g_Logger.LogCond<Logger::LogType::eInfo>(#expr, fmt::format(__VA_ARGS__), __FILE__, __LINE__) g_Logger.LogCond<aster::Logger::LogType::eInfo>(#expr, fmt::format(__VA_ARGS__), __FILE__, __LINE__)
#define ELSE_IF_ERROR(expr, ...) \ #define ELSE_IF_ERROR(expr, ...) \
; \ ; \
else if (Cast<bool>(expr)) \ else if (static_cast<bool>(expr)) \
[[unlikely]] g_Logger.LogCond<Logger::LogType::eError>(#expr, fmt::format(__VA_ARGS__), __FILE__, __LINE__) [[unlikely]] g_Logger.LogCond<aster::Logger::LogType::eError>(#expr, fmt::format(__VA_ARGS__), __FILE__, __LINE__)
#define ELSE_IF_WARN(expr, ...) \ #define ELSE_IF_WARN(expr, ...) \
; \ ; \
else if (Cast<bool>(expr)) \ else if (static_cast<bool>(expr)) \
[[unlikely]] g_Logger.LogCond<Logger::LogType::eWarning>(#expr, fmt::format(__VA_ARGS__), __FILE__, __LINE__) [[unlikely]] g_Logger.LogCond<aster::Logger::LogType::eWarning>(#expr, fmt::format(__VA_ARGS__), __FILE__, __LINE__)
#define ELSE_IF_INFO(expr, ...) \ #define ELSE_IF_INFO(expr, ...) \
; \ ; \
else if (Cast<bool>(expr)) \ else if (static_cast<bool>(expr)) \
g_Logger.LogCond<Logger::LogType::eInfo>(#expr, fmt::format(__VA_ARGS__), __FILE__, __LINE__) g_Logger.LogCond<aster::Logger::LogType::eInfo>(#expr, fmt::format(__VA_ARGS__), __FILE__, __LINE__)
#define ELSE_ERROR(...) \ #define ELSE_ERROR(...) \
; \ ; \
else [[unlikely]] g_Logger.Log<Logger::LogType::eError>(fmt::format(__VA_ARGS__), __FILE__, __LINE__) else [[unlikely]] g_Logger.Log<aster::Logger::LogType::eError>(fmt::format(__VA_ARGS__), __FILE__, __LINE__)
#define ELSE_WARN(...) \ #define ELSE_WARN(...) \
; \ ; \
else [[unlikely]] g_Logger.Log<Logger::LogType::eWarning>(fmt::format(__VA_ARGS__), __FILE__, __LINE__) else [[unlikely]] g_Logger.Log<aster::Logger::LogType::eWarning>(fmt::format(__VA_ARGS__), __FILE__, __LINE__)
#define ELSE_INFO(...) \ #define ELSE_INFO(...) \
; \ ; \
else g_Logger.Log<Logger::LogType::eInfo>(fmt::format(__VA_ARGS__), __FILE__, __LINE__) else g_Logger.Log<aster::Logger::LogType::eInfo>(fmt::format(__VA_ARGS__), __FILE__, __LINE__)
#if !defined(DEBUG_LOG_DISABLED) && !defined(ASTER_NDEBUG) #if !defined(DEBUG_LOG_DISABLED) && !defined(ASTER_NDEBUG)
#define DEBUG(...) g_Logger.Log<Logger::LogType::eDebug>(fmt::format(__VA_ARGS__), __FILE__, __LINE__) #define DEBUG(...) g_Logger.Log<aster::Logger::LogType::eDebug>(fmt::format(__VA_ARGS__), __FILE__, __LINE__)
#define DEBUG_IF(expr, ...) \ #define DEBUG_IF(expr, ...) \
if (Cast<bool>(expr)) \ if (static_cast<bool>(expr)) \
g_Logger.LogCond<Logger::LogType::eDebug>(#expr, fmt::format(__VA_ARGS__), __FILE__, __LINE__) g_Logger.LogCond<aster::Logger::LogType::eDebug>(#expr, fmt::format(__VA_ARGS__), __FILE__, __LINE__)
#define ELSE_IF_DEBUG(expr, ...) \ #define ELSE_IF_DEBUG(expr, ...) \
; \ ; \
else if (Cast<bool>(expr)) \ else if (static_cast<bool>(expr)) \
g_Logger.LogCond<Logger::LogType::eDebug>(#expr, fmt::format(__VA_ARGS__), __FILE__, __LINE__) g_Logger.LogCond<aster::Logger::LogType::eDebug>(#expr, fmt::format(__VA_ARGS__), __FILE__, __LINE__)
#define ELSE_DEBUG(...) \ #define ELSE_DEBUG(...) \
; \ ; \
else g_Logger.Log<Logger::LogType::eDebug>(fmt::format(__VA_ARGS__), __FILE__, __LINE__) else g_Logger.Log<aster::Logger::LogType::eDebug>(fmt::format(__VA_ARGS__), __FILE__, __LINE__)
#else // !defined(DEBUG_LOG_DISABLED) #else // !defined(DEBUG_LOG_DISABLED)
@ -172,17 +175,17 @@ extern Logger g_Logger;
#if !defined(VERBOSE_LOG_DISABLED) && !defined(ASTER_NDEBUG) #if !defined(VERBOSE_LOG_DISABLED) && !defined(ASTER_NDEBUG)
#define VERBOSE(...) g_Logger.Log<Logger::LogType::eVerbose>(fmt::format(__VA_ARGS__), __FILE__, __LINE__) #define VERBOSE(...) g_Logger.Log<aster::Logger::LogType::eVerbose>(fmt::format(__VA_ARGS__), __FILE__, __LINE__)
#define VERBOSE_IF(expr, ...) \ #define VERBOSE_IF(expr, ...) \
if (Cast<bool>(expr)) \ if (static_cast<bool>(expr)) \
g_Logger.LogCond<Logger::LogType::eVerbose>(#expr, fmt::format(__VA_ARGS__), __FILE__, __LINE__) g_Logger.LogCond<aster::Logger::LogType::eVerbose>(#expr, fmt::format(__VA_ARGS__), __FILE__, __LINE__)
#define ELSE_IF_VERBOSE(expr, ...) \ #define ELSE_IF_VERBOSE(expr, ...) \
; \ ; \
else if (Cast<bool>(expr)) \ else if (static_cast<bool>(expr)) \
g_Logger.LogCond<Logger::LogType::eVerbose>(#expr, fmt::format(__VA_ARGS__), __FILE__, __LINE__) g_Logger.LogCond<aster::Logger::LogType::eVerbose>(#expr, fmt::format(__VA_ARGS__), __FILE__, __LINE__)
#define ELSE_VERBOSE(...) \ #define ELSE_VERBOSE(...) \
; \ ; \
else g_Logger.Log<Logger::LogType::eVerbose>(fmt::format(__VA_ARGS__), __FILE__, __LINE__) else g_Logger.Log<aster::Logger::LogType::eVerbose>(fmt::format(__VA_ARGS__), __FILE__, __LINE__)
#else // !defined(DEBUG_LOG_DISABLED) #else // !defined(DEBUG_LOG_DISABLED)
@ -207,5 +210,5 @@ extern Logger g_Logger;
#endif // !defined(VERBOSE_LOG_DISABLED) #endif // !defined(VERBOSE_LOG_DISABLED)
#define DO(code) , code #define DO(code) , code
#define ABORT(code) exit(Cast<i32>(code)) #define ABORT(code) exit(static_cast<int>(code))
#define THEN_ABORT(code) , ABORT(code) #define THEN_ABORT(code) , ABORT(code)

View File

@ -5,7 +5,7 @@ cmake_minimum_required(VERSION 3.13)
target_sources(aster_core target_sources(aster_core
PRIVATE PRIVATE
"global.cpp" "global.cpp"
"context.cpp" "instance.cpp"
"physical_device.cpp" "physical_device.cpp"
"device.cpp" "device.cpp"
"swapchain.cpp" "swapchain.cpp"
@ -13,4 +13,5 @@ PRIVATE
"buffer.cpp" "buffer.cpp"
"image.cpp" "image.cpp"
"surface.cpp" "surface.cpp"
"window.cpp") "window.cpp"
"sampler.cpp")

View File

@ -1,35 +1,27 @@
// ============================================= // =============================================
// Aster: buffer.cpp // Aster: buffer.cpp
// Copyright (c) 2020-2024 Anish Bhobe // Copyright (c) 2020-2025 Anish Bhobe
// ============================================= // =============================================
#include "core/buffer.h" #include "aster/core/buffer.h"
#include "core/device.h" #include "aster/core/device.h"
void using namespace aster;
Buffer::Destroy(const Device *device)
Buffer::Buffer(Device const *device, usize const size, vk::BufferUsageFlags const bufferUsage,
VmaAllocationCreateFlags const allocationFlags, VmaMemoryUsage const memoryUsage, cstr const name)
{ {
if (!IsValid() || !IsOwned()) assert(!m_Buffer);
return;
vmaDestroyBuffer(device->m_Allocator, m_Buffer, m_Allocation); m_Device = device;
m_Size_ = 0;
}
void
Buffer::Allocate(const Device *device, usize size, vk::BufferUsageFlags bufferUsage,
VmaAllocationCreateFlags allocationFlags, VmaMemoryUsage memoryUsage, cstr name)
{
assert(!IsValid());
assert(size <= SIZE_MASK);
vk::BufferCreateInfo bufferCreateInfo = { vk::BufferCreateInfo bufferCreateInfo = {
.size = size, .size = size,
.usage = bufferUsage, .usage = bufferUsage | vk::BufferUsageFlagBits::eShaderDeviceAddress,
.sharingMode = vk::SharingMode::eExclusive, .sharingMode = vk::SharingMode::eExclusive,
}; };
const VmaAllocationCreateInfo allocationCreateInfo = { VmaAllocationCreateInfo const allocationCreateInfo = {
.flags = allocationFlags, .flags = allocationFlags,
.usage = memoryUsage, .usage = memoryUsage,
}; };
@ -37,156 +29,88 @@ Buffer::Allocate(const Device *device, usize size, vk::BufferUsageFlags bufferUs
VkBuffer buffer; VkBuffer buffer;
VmaAllocation allocation; VmaAllocation allocation;
VmaAllocationInfo allocationInfo; VmaAllocationInfo allocationInfo;
auto result = Cast<vk::Result>(vmaCreateBuffer(device->m_Allocator, Recast<VkBufferCreateInfo *>(&bufferCreateInfo), auto result = static_cast<vk::Result>(
&allocationCreateInfo, &buffer, &allocation, &allocationInfo)); vmaCreateBuffer(device->m_Allocator, reinterpret_cast<VkBufferCreateInfo *>(&bufferCreateInfo),
&allocationCreateInfo, &buffer, &allocation, &allocationInfo));
ERROR_IF(Failed(result), "Could not allocate buffer. Cause: {}", result) THEN_ABORT(result); ERROR_IF(Failed(result), "Could not allocate buffer. Cause: {}", result) THEN_ABORT(result);
vk::MemoryPropertyFlags memoryPropertyFlags; // vk::MemoryPropertyFlags memoryPropertyFlags;
vmaGetAllocationMemoryProperties(device->m_Allocator, allocation, // vmaGetAllocationMemoryProperties(device->m_Allocator, allocation, Recast<VkMemoryPropertyFlags
Recast<VkMemoryPropertyFlags *>(&memoryPropertyFlags)); // *>(&memoryPropertyFlags));
// TODO: Actually track Host Access // TODO: Actually track Host Access
// bool hostAccessible = Cast<bool>(memoryPropertyFlags & vk::MemoryPropertyFlagBits::eHostVisible); // bool hostAccessible = static_cast<bool>(memoryPropertyFlags & vk::MemoryPropertyFlagBits::eHostVisible);
m_Buffer = buffer; m_Buffer = buffer;
m_Size_ = size | VALID_BUFFER_BIT | OWNED_BIT; m_Size = size;
m_Allocation = allocation; m_Allocation = allocation;
m_Mapped = Cast<u8 *>(allocationInfo.pMappedData); m_Mapped = static_cast<u8 *>(allocationInfo.pMappedData);
m_Flags = {};
if (bufferUsage & vk::BufferUsageFlagBits::eTransferSrc)
m_Flags |= FlagBits::eStaging;
if (bufferUsage & vk::BufferUsageFlagBits::eIndexBuffer)
m_Flags |= FlagBits::eIndex;
if (bufferUsage & vk::BufferUsageFlagBits::eIndirectBuffer)
m_Flags |= FlagBits::eIndirect;
if (bufferUsage & vk::BufferUsageFlagBits::eVertexBuffer)
m_Flags |= FlagBits::eVertex;
if (bufferUsage & vk::BufferUsageFlagBits::eUniformBuffer)
m_Flags |= FlagBits::eUniform;
if (bufferUsage & vk::BufferUsageFlagBits::eStorageBuffer)
m_Flags |= FlagBits::eStorage;
vk::BufferDeviceAddressInfo const addressInfo = {.buffer = m_Buffer};
m_DeviceAddr = m_Device->m_Device.getBufferAddress(&addressInfo);
device->SetName(m_Buffer, name); device->SetName(m_Buffer, name);
} }
uptr Buffer::Buffer(Buffer &&other) noexcept
Buffer::GetDeviceAddress(const Device *device) : m_Device{Take(other.m_Device)}
, m_Buffer{Take(other.m_Buffer)}
, m_Allocation{Take(other.m_Allocation)}
, m_Mapped{Take(other.m_Mapped)}
, m_DeviceAddr{Take(other.m_DeviceAddr)}
, m_Size{Take(other.m_Size)}
{ {
vk::BufferDeviceAddressInfo addressInfo = {.buffer = m_Buffer}; }
return device->m_Device.getBufferAddress(&addressInfo);
Buffer &
Buffer::operator=(Buffer &&other) noexcept
{
if (this == &other)
return *this;
using std::swap;
swap(m_Device, other.m_Device);
swap(m_Buffer, other.m_Buffer);
swap(m_Allocation, other.m_Allocation);
swap(m_Mapped, other.m_Mapped);
swap(m_DeviceAddr, other.m_DeviceAddr);
swap(m_Size, other.m_Size);
return *this;
}
Buffer::~Buffer()
{
if (!m_Buffer)
return;
vmaDestroyBuffer(m_Device->m_Allocator, Take(m_Buffer), m_Allocation);
m_Size = 0;
}
uptr
Buffer::GetDeviceAddress() const
{
return m_DeviceAddr;
} }
void void
Buffer::Write(const Device *device, usize offset, usize size, const void *data) Buffer::Write(usize const offset, usize const size, void const *data) const
{ {
assert(IsHostVisible()); assert(IsMapped());
memcpy(m_Mapped + offset, data, size);
if (!IsMapped())
{
void *mapped;
auto result = Cast<vk::Result>(vmaMapMemory(device->m_Allocator, m_Allocation, &mapped));
ERROR_IF(Failed(result), "Memory mapping failed. Cause: {}", result);
if (!Failed(result))
{
m_Mapped = Cast<u8 *>(mapped);
memcpy(m_Mapped + offset, data, size);
vmaUnmapMemory(device->m_Allocator, m_Allocation);
m_Mapped = nullptr;
}
}
else
{
memcpy(m_Mapped + offset, data, size);
}
// TODO: Debug this. // TODO: Debug this.
// auto result = Cast<vk::Result>(vmaCopyMemoryToAllocation(device->m_Allocator, &data, m_Allocation, 0, size)); // auto result = static_cast<vk::Result>(vmaCopyMemoryToAllocation(device->m_Allocator, &data, m_Allocation, 0,
// ERROR_IF(Failed(result), "Writing to buffer failed. Cause: {}", result) THEN_ABORT(result); // size)); ERROR_IF(Failed(result), "Writing to buffer failed. Cause: {}", result) THEN_ABORT(result);
}
void
UniformBuffer::Init(const Device *device, const usize size, const cstr name)
{
Allocate(device, size, vk::BufferUsageFlagBits::eUniformBuffer,
VMA_ALLOCATION_CREATE_HOST_ACCESS_SEQUENTIAL_WRITE_BIT |
VMA_ALLOCATION_CREATE_HOST_ACCESS_ALLOW_TRANSFER_INSTEAD_BIT | VMA_ALLOCATION_CREATE_MAPPED_BIT,
VMA_MEMORY_USAGE_AUTO, name);
}
void
StorageBuffer::Init(const Device *device, usize size, bool hostVisible, cstr name)
{
Init(device, size, hostVisible, false, name);
}
void
StorageBuffer::Init(const Device *device, usize size, bool hostVisible, bool deviceAddress, cstr name)
{
vk::BufferUsageFlags usage = vk::BufferUsageFlagBits::eStorageBuffer;
if (deviceAddress)
{
usage |= vk::BufferUsageFlagBits::eShaderDeviceAddress;
}
if (hostVisible)
{
Allocate(device, size, usage,
VMA_ALLOCATION_CREATE_HOST_ACCESS_SEQUENTIAL_WRITE_BIT |
VMA_ALLOCATION_CREATE_HOST_ACCESS_ALLOW_TRANSFER_INSTEAD_BIT | VMA_ALLOCATION_CREATE_MAPPED_BIT,
VMA_MEMORY_USAGE_AUTO, name);
}
else
{
usage |= vk::BufferUsageFlagBits::eTransferDst;
Allocate(device, size, usage, 0,
VMA_MEMORY_USAGE_AUTO, name);
}
}
void
StorageIndexBuffer::Init(const Device *device, usize size, bool hostVisible, bool deviceAddress, cstr name)
{
vk::BufferUsageFlags usage = vk::BufferUsageFlagBits::eStorageBuffer | vk::BufferUsageFlagBits::eIndexBuffer;
if (deviceAddress)
{
usage |= vk::BufferUsageFlagBits::eShaderDeviceAddress;
}
if (hostVisible)
{
Allocate(device, size, usage,
VMA_ALLOCATION_CREATE_HOST_ACCESS_SEQUENTIAL_WRITE_BIT |
VMA_ALLOCATION_CREATE_HOST_ACCESS_ALLOW_TRANSFER_INSTEAD_BIT | VMA_ALLOCATION_CREATE_MAPPED_BIT,
VMA_MEMORY_USAGE_AUTO, name);
}
else
{
usage |= vk::BufferUsageFlagBits::eTransferDst;
Allocate(device, size, usage, 0, VMA_MEMORY_USAGE_AUTO, name);
}
}
void
IndirectBuffer::Init(const Device *device, usize size, bool hostVisible, cstr name)
{
vk::BufferUsageFlags usage = vk::BufferUsageFlagBits::eStorageBuffer | vk::BufferUsageFlagBits::eIndirectBuffer | vk::BufferUsageFlagBits::eShaderDeviceAddress;
if (hostVisible)
{
Allocate(device, size, usage,
VMA_ALLOCATION_CREATE_HOST_ACCESS_SEQUENTIAL_WRITE_BIT |
VMA_ALLOCATION_CREATE_HOST_ACCESS_ALLOW_TRANSFER_INSTEAD_BIT | VMA_ALLOCATION_CREATE_MAPPED_BIT,
VMA_MEMORY_USAGE_AUTO, name);
}
else
{
usage |= vk::BufferUsageFlagBits::eTransferDst;
Allocate(device, size, usage, 0, VMA_MEMORY_USAGE_AUTO, name);
}
}
void
VertexBuffer::Init(const Device *device, usize size, cstr name)
{
Allocate(device, size, vk::BufferUsageFlagBits::eVertexBuffer | vk::BufferUsageFlagBits::eTransferDst,
0, VMA_MEMORY_USAGE_AUTO, name);
}
void
IndexBuffer::Init(const Device *device, usize size, cstr name)
{
Allocate(device, size, vk::BufferUsageFlagBits::eIndexBuffer | vk::BufferUsageFlagBits::eTransferDst,
0, VMA_MEMORY_USAGE_AUTO, name);
}
void
StagingBuffer::Init(const Device *device, usize size, cstr name)
{
Allocate(device, size, vk::BufferUsageFlagBits::eTransferSrc,
VMA_ALLOCATION_CREATE_HOST_ACCESS_SEQUENTIAL_WRITE_BIT | VMA_ALLOCATION_CREATE_MAPPED_BIT,
VMA_MEMORY_USAGE_AUTO, name);
} }

View File

@ -1,34 +1,30 @@
// ============================================= // =============================================
// Aster: device.cpp // Aster: device.cpp
// Copyright (c) 2020-2024 Anish Bhobe // Copyright (c) 2020-2025 Anish Bhobe
// ============================================= // =============================================
#include "core/device.h" #include "aster/core/device.h"
#include "core/context.h" #include "aster/core/instance.h"
#include "core/physical_device.h" #include "aster/core/physical_device.h"
#include "core/queue_allocation.h" #include "aster/core/queue_allocation.h"
#include <EASTL/array.h> #include <EASTL/array.h>
#include <EASTL/fixed_vector.h> #include <EASTL/fixed_vector.h>
using namespace aster;
// TODO: This will need to be flexible for devices that don't support some of the extensions. // TODO: This will need to be flexible for devices that don't support some of the extensions.
constexpr eastl::array DEVICE_EXTENSIONS = { constexpr eastl::array DEVICE_EXTENSIONS = {
VK_KHR_SWAPCHAIN_EXTENSION_NAME, VK_KHR_SWAPCHAIN_EXTENSION_NAME,
}; };
Device::Device(const Context *context, PhysicalDevice *physicalDevice, Features *enabledFeatures, Device::Device(Instance const &context, PhysicalDevice &physicalDevice, Features &enabledFeatures,
const eastl::vector<QueueAllocation> &queueAllocations, NameString &&name) eastl::span<QueueAllocation> const &queueAllocations, eastl::span<u8> const &pipelineCacheData,
: Device(context, physicalDevice, enabledFeatures, queueAllocations, {}, std::move(name))
{
}
Device::Device(const Context *context, PhysicalDevice *physicalDevice, Features *enabledFeatures,
const eastl::vector<QueueAllocation> &queueAllocations, eastl::span<u8> &&pipelineCacheData,
NameString &&name) NameString &&name)
: m_Name(std::move(name)) : m_Name(std::move(name))
, m_PhysicalDevice(physicalDevice->m_PhysicalDevice) , m_PhysicalDevice(physicalDevice.m_PhysicalDevice)
, m_ValidationEnabled(context->m_DebugMessenger != nullptr) , m_ValidationEnabled(context.m_DebugMessenger != nullptr)
{ {
// Shouldn't have more than 4 deviceQueueFamilies in use anyway. Else we can heap // Shouldn't have more than 4 deviceQueueFamilies in use anyway. Else we can heap
eastl::fixed_vector<vk::DeviceQueueCreateInfo, 4> deviceQueueCreateInfos; eastl::fixed_vector<vk::DeviceQueueCreateInfo, 4> deviceQueueCreateInfos;
@ -51,19 +47,19 @@ Device::Device(const Context *context, PhysicalDevice *physicalDevice, Features
}); });
} }
vk::PhysicalDeviceFeatures *deviceFeatures = &enabledFeatures->m_Vulkan10Features; vk::PhysicalDeviceFeatures *deviceFeatures = &enabledFeatures.m_Vulkan10Features;
vk::PhysicalDeviceVulkan11Features *vulkan11Features = &enabledFeatures->m_Vulkan11Features; vk::PhysicalDeviceVulkan11Features *vulkan11Features = &enabledFeatures.m_Vulkan11Features;
vk::PhysicalDeviceVulkan12Features *vulkan12Features = &enabledFeatures->m_Vulkan12Features; vk::PhysicalDeviceVulkan12Features *vulkan12Features = &enabledFeatures.m_Vulkan12Features;
vk::PhysicalDeviceVulkan13Features *vulkan13Features = &enabledFeatures->m_Vulkan13Features; vk::PhysicalDeviceVulkan13Features *vulkan13Features = &enabledFeatures.m_Vulkan13Features;
vulkan11Features->pNext = vulkan12Features; vulkan11Features->pNext = vulkan12Features;
vulkan12Features->pNext = vulkan13Features; vulkan12Features->pNext = vulkan13Features;
vk::DeviceCreateInfo deviceCreateInfo = { vk::DeviceCreateInfo deviceCreateInfo = {
.pNext = vulkan11Features, .pNext = vulkan11Features,
.queueCreateInfoCount = Cast<u32>(deviceQueueCreateInfos.size()), .queueCreateInfoCount = static_cast<u32>(deviceQueueCreateInfos.size()),
.pQueueCreateInfos = deviceQueueCreateInfos.data(), .pQueueCreateInfos = deviceQueueCreateInfos.data(),
.enabledExtensionCount = Cast<u32>(DEVICE_EXTENSIONS.size()), .enabledExtensionCount = static_cast<u32>(DEVICE_EXTENSIONS.size()),
.ppEnabledExtensionNames = DEVICE_EXTENSIONS.data(), .ppEnabledExtensionNames = DEVICE_EXTENSIONS.data(),
.pEnabledFeatures = deviceFeatures, .pEnabledFeatures = deviceFeatures,
}; };
@ -71,25 +67,25 @@ Device::Device(const Context *context, PhysicalDevice *physicalDevice, Features
vk::Result result = m_PhysicalDevice.createDevice(&deviceCreateInfo, nullptr, &m_Device); vk::Result result = m_PhysicalDevice.createDevice(&deviceCreateInfo, nullptr, &m_Device);
ERROR_IF(Failed(result), "Could not initialize Vulkan Device. Cause: {}", result) ERROR_IF(Failed(result), "Could not initialize Vulkan Device. Cause: {}", result)
THEN_ABORT(result) THEN_ABORT(result)
ELSE_DEBUG("{} ({}) Initialized.", m_Name, physicalDevice->m_DeviceProperties.deviceName.data()); ELSE_DEBUG("{} ({}) Initialized.", m_Name, physicalDevice.m_DeviceProperties.deviceName.data());
SetName(m_Device, m_Name.data()); SetName(m_Device, m_Name.data());
VmaVulkanFunctions vmaVulkanFunctions = { VmaVulkanFunctions vmaVulkanFunctions = {
.vkGetInstanceProcAddr = vk::defaultDispatchLoaderDynamic.vkGetInstanceProcAddr, .vkGetInstanceProcAddr = vk::detail::defaultDispatchLoaderDynamic.vkGetInstanceProcAddr,
.vkGetDeviceProcAddr = vk::defaultDispatchLoaderDynamic.vkGetDeviceProcAddr, .vkGetDeviceProcAddr = vk::detail::defaultDispatchLoaderDynamic.vkGetDeviceProcAddr,
}; };
const VmaAllocatorCreateInfo allocatorCreateInfo = { VmaAllocatorCreateInfo const allocatorCreateInfo = {
.flags = VMA_ALLOCATOR_CREATE_BUFFER_DEVICE_ADDRESS_BIT, .flags = VMA_ALLOCATOR_CREATE_BUFFER_DEVICE_ADDRESS_BIT,
.physicalDevice = m_PhysicalDevice, .physicalDevice = m_PhysicalDevice,
.device = m_Device, .device = m_Device,
.pVulkanFunctions = &vmaVulkanFunctions, .pVulkanFunctions = &vmaVulkanFunctions,
.instance = context->m_Instance, .instance = context.m_Instance,
.vulkanApiVersion = ASTER_API_VERSION, .vulkanApiVersion = ASTER_API_VERSION,
}; };
result = Cast<vk::Result>(vmaCreateAllocator(&allocatorCreateInfo, &m_Allocator)); result = static_cast<vk::Result>(vmaCreateAllocator(&allocatorCreateInfo, &m_Allocator));
ERROR_IF(Failed(result), "Memory allocator creation failed. Cause: {}", result) ERROR_IF(Failed(result), "Memory allocator creation failed. Cause: {}", result)
DO(m_Device.destroy(nullptr)) DO(m_Device.destroy(nullptr))
THEN_ABORT(result) THEN_ABORT(result)
@ -110,6 +106,9 @@ Device::Device(const Context *context, PhysicalDevice *physicalDevice, Features
Device::~Device() Device::~Device()
{ {
if (!m_Device)
return;
m_Device.destroy(m_PipelineCache, nullptr); m_Device.destroy(m_PipelineCache, nullptr);
if (m_Allocator) if (m_Allocator)
{ {
@ -123,7 +122,7 @@ Device::~Device()
} }
vk::Queue vk::Queue
Device::GetQueue(const u32 familyIndex, const u32 queueIndex) const Device::GetQueue(u32 const familyIndex, u32 const queueIndex) const
{ {
vk::Queue queue; vk::Queue queue;
m_Device.getQueue(familyIndex, queueIndex, &queue); m_Device.getQueue(familyIndex, queueIndex, &queue);
@ -156,6 +155,7 @@ Device::Device(Device &&other) noexcept
, m_PhysicalDevice(Take(other.m_PhysicalDevice)) , m_PhysicalDevice(Take(other.m_PhysicalDevice))
, m_Device(Take(other.m_Device)) , m_Device(Take(other.m_Device))
, m_Allocator(Take(other.m_Allocator)) , m_Allocator(Take(other.m_Allocator))
, m_PipelineCache(Take(other.m_PipelineCache))
{ {
} }
@ -168,5 +168,6 @@ Device::operator=(Device &&other) noexcept
m_PhysicalDevice = Take(other.m_PhysicalDevice); m_PhysicalDevice = Take(other.m_PhysicalDevice);
m_Device = Take(other.m_Device); m_Device = Take(other.m_Device);
m_Allocator = Take(other.m_Allocator); m_Allocator = Take(other.m_Allocator);
m_PipelineCache = Take(other.m_PipelineCache);
return *this; return *this;
} }

View File

@ -1,9 +1,9 @@
// ============================================= // =============================================
// Aster: global.cpp // Aster: global.cpp
// Copyright (c) 2020-2024 Anish Bhobe // Copyright (c) 2020-2025 Anish Bhobe
// ============================================= // =============================================
#include "core/global.h" #include "aster/core/global.h"
#include <cstdarg> #include <cstdarg>
#include <cstdio> #include <cstdio>
@ -14,6 +14,8 @@
// NOTE: Vulkan Dispatch Loader Storage - Should only appear once. // NOTE: Vulkan Dispatch Loader Storage - Should only appear once.
VULKAN_HPP_DEFAULT_DISPATCH_LOADER_DYNAMIC_STORAGE VULKAN_HPP_DEFAULT_DISPATCH_LOADER_DYNAMIC_STORAGE
using namespace aster;
struct MemorySize struct MemorySize
{ {
u16 m_Gigabytes; u16 m_Gigabytes;
@ -26,11 +28,11 @@ struct MemorySize
{ {
usize totalBytes = bytes + m_Bytes; usize totalBytes = bytes + m_Bytes;
m_Bytes = totalBytes % 1024; m_Bytes = totalBytes % 1024;
const usize totalKb = m_Kilobytes + totalBytes / 1024; usize const totalKb = m_Kilobytes + totalBytes / 1024;
m_Kilobytes = totalKb % 1024; m_Kilobytes = totalKb % 1024;
const usize totalMb = m_Megabytes + totalKb / 1024; usize const totalMb = m_Megabytes + totalKb / 1024;
m_Megabytes = totalMb % 1024; m_Megabytes = totalMb % 1024;
m_Gigabytes += Cast<u16>(totalMb / 1024); m_Gigabytes += static_cast<u16>(totalMb / 1024);
return *this; return *this;
} }
@ -56,23 +58,23 @@ struct fmt::formatter<MemorySize>
// return format_to(ctx.out(), "({}, {})", foo.a, foo.b); // --== KEY LINE ==-- // return format_to(ctx.out(), "({}, {})", foo.a, foo.b); // --== KEY LINE ==--
if (mem.m_Gigabytes > 0) if (mem.m_Gigabytes > 0)
{ {
return v10::format_to(ctx.out(), "{}.{} GB", mem.m_Gigabytes, Cast<u16>(mem.m_Megabytes / 1024.0)); return fmt::format_to(ctx.out(), "{}.{} GB", mem.m_Gigabytes, static_cast<u16>(mem.m_Megabytes / 1024.0));
} }
if (mem.m_Megabytes > 0) if (mem.m_Megabytes > 0)
{ {
return v10::format_to(ctx.out(), "{}.{} MB", mem.m_Megabytes, Cast<u16>(mem.m_Kilobytes / 1024.0)); return fmt::format_to(ctx.out(), "{}.{} MB", mem.m_Megabytes, static_cast<u16>(mem.m_Kilobytes / 1024.0));
} }
if (mem.m_Kilobytes > 0) if (mem.m_Kilobytes > 0)
{ {
return v10::format_to(ctx.out(), "{}.{} KB", mem.m_Kilobytes, Cast<u16>(mem.m_Bytes / 1024.0)); return fmt::format_to(ctx.out(), "{}.{} KB", mem.m_Kilobytes, static_cast<u16>(mem.m_Bytes / 1024.0));
} }
return v10::format_to(ctx.out(), "{} Bytes", mem.m_Bytes); return fmt::format_to(ctx.out(), "{} Bytes", mem.m_Bytes);
} }
}; };
void * void *
operator new[](size_t size, const char * /*pName*/, int flags, unsigned /*debugFlags*/, const char * /*file*/, operator new[](size_t size, char const * /*pName*/, int flags, unsigned /*debugFlags*/, char const * /*file*/,
int /*line*/) int /*line*/)
{ {
g_TotalAlloc += size; g_TotalAlloc += size;
@ -82,8 +84,8 @@ operator new[](size_t size, const char * /*pName*/, int flags, unsigned /*debugF
} }
void * void *
operator new[](size_t size, size_t /*alignment*/, size_t /*alignmentOffset*/, const char * /*pName*/, int flags, operator new[](size_t size, size_t /*alignment*/, size_t /*alignmentOffset*/, char const * /*pName*/, int flags,
unsigned /*debugFlags*/, const char * /*file*/, int /*line*/) unsigned /*debugFlags*/, char const * /*file*/, int /*line*/)
{ {
g_TotalAlloc += size; g_TotalAlloc += size;

View File

@ -1,425 +1,484 @@
// ============================================= // =============================================
// Aster: image.cpp // Aster: image.cpp
// Copyright (c) 2020-2024 Anish Bhobe // Copyright (c) 2020-2025 Anish Bhobe
// ============================================= // =============================================
#include "core/image.h" #include "aster/core/image.h"
#include "core/device.h" #include "aster/core/device.h"
void using namespace aster;
Image::Destroy(const Device *device)
Image &
Image::operator=(Image &&other) noexcept
{ {
if (!IsValid() || !IsOwned()) if (this == &other)
{ return *this;
m_Flags_ = 0; using std::swap;
swap(m_Device, other.m_Device);
swap(m_Image, other.m_Image);
swap(m_Allocation, other.m_Allocation);
swap(m_Extent, other.m_Extent);
swap(m_Format, other.m_Format);
swap(m_EmptyPadding_, other.m_EmptyPadding_);
swap(m_Flags_, other.m_Flags_);
swap(m_LayerCount, other.m_LayerCount);
swap(m_MipLevels, other.m_MipLevels);
return *this;
}
Image::~Image()
{
if (!IsValid())
return; return;
}
device->m_Device.destroy(m_View, nullptr); vmaDestroyImage(m_Device->m_Allocator, Take(m_Image), m_Allocation);
vmaDestroyImage(device->m_Allocator, m_Image, m_Allocation); m_Flags_ = {};
m_Flags_ = 0;
} }
void void
Texture::Init(const Device *device, const vk::Extent2D extent, vk::Format imageFormat, const bool isMipMapped, Image::DestroyView(vk::ImageView const imageView) const
const cstr name)
{ {
WARN_IF(!IsPowerOfTwo(extent.width) || !IsPowerOfTwo(extent.width), "Image {2} is {0}x{1} (Non Power of Two)", m_Device->m_Device.destroy(imageView, nullptr);
extent.width, extent.height, name ? name : "<unnamed>");
const u8 mipLevels = isMipMapped ? 1 + Cast<u8>(floor(log2(eastl::max(extent.width, extent.height)))) : 1;
auto usage = vk::ImageUsageFlagBits::eSampled | vk::ImageUsageFlagBits::eTransferDst;
if (isMipMapped)
{
usage |= vk::ImageUsageFlagBits::eTransferSrc;
}
vk::ImageCreateInfo imageCreateInfo = {
.imageType = vk::ImageType::e2D,
.format = imageFormat,
.extent = ToExtent3D(extent, 1),
.mipLevels = mipLevels,
.arrayLayers = 1,
.samples = vk::SampleCountFlagBits::e1,
.tiling = vk::ImageTiling::eOptimal,
.usage = usage,
.sharingMode = vk::SharingMode::eExclusive,
.initialLayout = vk::ImageLayout::eUndefined,
};
constexpr VmaAllocationCreateInfo allocationCreateInfo = {
.flags = {},
.usage = VMA_MEMORY_USAGE_AUTO,
};
VkImage image;
VmaAllocation allocation;
auto result = Cast<vk::Result>(vmaCreateImage(device->m_Allocator, Recast<VkImageCreateInfo *>(&imageCreateInfo),
&allocationCreateInfo, &image, &allocation, nullptr));
ERROR_IF(Failed(result), "Could not allocate image {}. Cause: {}", name, result) THEN_ABORT(result);
vk::ImageView view;
vk::ImageViewCreateInfo imageViewCreateInfo = {
.image = image,
.viewType = vk::ImageViewType::e2D,
.format = imageFormat,
.components = {},
.subresourceRange =
{
.aspectMask = vk::ImageAspectFlagBits::eColor,
.baseMipLevel = 0,
.levelCount = mipLevels,
.baseArrayLayer = 0,
.layerCount = 1,
},
};
result = device->m_Device.createImageView(&imageViewCreateInfo, nullptr, &view);
ERROR_IF(Failed(result), "Could not create image view {}. Cause: {}", name, result) THEN_ABORT(result);
m_Image = image;
m_View = view;
m_Allocation = allocation;
m_Extent = imageCreateInfo.extent;
m_Flags_ = OWNED_BIT | VALID_BIT;
m_LayerCount = 1;
m_MipLevels = mipLevels;
device->SetName(m_Image, name);
} }
/* //
Cube map Faces info. // void
// Texture::Init(const Device *device, const vk::Extent2D extent, vk::Format imageFormat, const bool isMipMapped,
TODO: Correct this based on the actual layout for upside down viewport. // const cstr name)
//{
| Axis | Layer | Up | // WARN_IF(!IsPowerOfTwo(extent.width) || !IsPowerOfTwo(extent.width), "Image {2} is {0}x{1} (Non Power of Two)",
|:----:|:-----:|:--:| // extent.width, extent.height, name ? name : "<unnamed>");
| +x | 0 | -y | //
| -x | 1 | -y | // const u8 mipLevels = isMipMapped ? 1 + static_cast<u8>(floor(log2(eastl::max(extent.width, extent.height)))) : 1;
| +y | 2 | +z | //
| -y | 3 | -z | // auto usage = vk::ImageUsageFlagBits::eSampled | vk::ImageUsageFlagBits::eTransferDst;
| +z | 4 | -y | // if (isMipMapped)
| -z | 5 | -y | // {
// usage |= vk::ImageUsageFlagBits::eTransferSrc;
Remember, we use upside down viewport. // }
//
*/ // vk::ImageCreateInfo imageCreateInfo = {
// .imageType = vk::ImageType::e2D,
void // .format = imageFormat,
TextureCube::Init(const Device *device, u32 cubeSide, vk::Format imageFormat, bool isMipMapped, cstr name) // .extent = ToExtent3D(extent, 1),
// .mipLevels = mipLevels,
// .arrayLayers = 1,
// .samples = vk::SampleCountFlagBits::e1,
// .tiling = vk::ImageTiling::eOptimal,
// .usage = usage,
// .sharingMode = vk::SharingMode::eExclusive,
// .initialLayout = vk::ImageLayout::eUndefined,
// };
// constexpr VmaAllocationCreateInfo allocationCreateInfo = {
// .flags = {},
// .usage = VMA_MEMORY_USAGE_AUTO,
// };
//
// VkImage image;
// VmaAllocation allocation;
// auto result = static_cast<vk::Result>(vmaCreateImage(device->m_Allocator, reinterpret_cast<VkImageCreateInfo
// *>(&imageCreateInfo),
// &allocationCreateInfo, &image, &allocation, nullptr));
// ERROR_IF(Failed(result), "Could not allocate image {}. Cause: {}", name, result) THEN_ABORT(result);
//
// vk::ImageView view;
// vk::ImageViewCreateInfo imageViewCreateInfo = {
// .image = image,
// .viewType = vk::ImageViewType::e2D,
// .format = imageFormat,
// .components = {},
// .subresourceRange =
// {
// .aspectMask = vk::ImageAspectFlagBits::eColor,
// .baseMipLevel = 0,
// .levelCount = mipLevels,
// .baseArrayLayer = 0,
// .layerCount = 1,
// },
// };
// result = device->m_Device.createImageView(&imageViewCreateInfo, nullptr, &view);
// ERROR_IF(Failed(result), "Could not create image view {}. Cause: {}", name, result) THEN_ABORT(result);
//
// m_Device = device;
// m_Image = image;
// m_View = view;
// m_Allocation = allocation;
// m_Extent = imageCreateInfo.extent;
// m_LayerCount = 1;
// m_MipLevels = mipLevels;
//
// device->SetName(m_Image, name);
//}
//
///*
// Cube map Faces info.
//
// TODO: Correct this based on the actual layout for upside down viewport.
//
//| Axis | Layer | Up |
//|:----:|:-----:|:--:|
//| +x | 0 | -y |
//| -x | 1 | -y |
//| +y | 2 | +z |
//| -y | 3 | -z |
//| +z | 4 | -y |
//| -z | 5 | -y |
//
// Remember, we use upside down viewport.
//
//*/
//
// void
// TextureCube::Init(const Device *device, u32 cubeSide, vk::Format imageFormat, bool isMipMapped, cstr name)
//{
// WARN_IF(!IsPowerOfTwo(cubeSide), "Image Cube {1} has side {0}x{0} (Non Power of Two)", cubeSide,
// name ? name : "<unnamed>");
//
// const u8 mipLevels = isMipMapped ? 1 + static_cast<u8>(floor(log2(cubeSide))) : 1;
//
// auto usage = vk::ImageUsageFlagBits::eSampled | vk::ImageUsageFlagBits::eTransferDst;
// if (isMipMapped)
// {
// usage |= vk::ImageUsageFlagBits::eTransferSrc;
// }
//
// const vk::Extent3D extent = {.width = cubeSide, .height = cubeSide, .depth = 1};
//
// vk::ImageCreateInfo imageCreateInfo = {
// .flags = vk::ImageCreateFlagBits::eCubeCompatible,
// .imageType = vk::ImageType::e2D,
// .format = imageFormat,
// .extent = extent,
// .mipLevels = mipLevels,
// .arrayLayers = 6,
// .samples = vk::SampleCountFlagBits::e1,
// .tiling = vk::ImageTiling::eOptimal,
// .usage = usage,
// .sharingMode = vk::SharingMode::eExclusive,
// .initialLayout = vk::ImageLayout::eUndefined,
// };
// constexpr VmaAllocationCreateInfo allocationCreateInfo = {
// .flags = {},
// .usage = VMA_MEMORY_USAGE_AUTO,
// };
//
// VkImage image;
// VmaAllocation allocation;
// auto result = static_cast<vk::Result>(vmaCreateImage(device->m_Allocator, reinterpret_cast<VkImageCreateInfo
// *>(&imageCreateInfo),
// &allocationCreateInfo, &image, &allocation, nullptr));
// ERROR_IF(Failed(result), "Could not allocate image {}. Cause: {}", name, result) THEN_ABORT(result);
//
// vk::ImageView view;
// vk::ImageViewCreateInfo imageViewCreateInfo = {
// .image = image,
// .viewType = vk::ImageViewType::eCube,
// .format = imageFormat,
// .components = {},
// .subresourceRange =
// {
// .aspectMask = vk::ImageAspectFlagBits::eColor,
// .baseMipLevel = 0,
// .levelCount = mipLevels,
// .baseArrayLayer = 0,
// .layerCount = 6,
// },
// };
// result = device->m_Device.createImageView(&imageViewCreateInfo, nullptr, &view);
// ERROR_IF(Failed(result), "Could not create image view {}. Cause: {}", name, result) THEN_ABORT(result);
//
// m_Device = device;
// m_Image = image;
// m_View = view;
// m_Allocation = allocation;
// m_Extent = extent;
// m_MipLevels = mipLevels;
// m_LayerCount = 6;
//
// device->SetName(m_Image, name);
// }
//
// void
// AttachmentImage::Init(const Device *device, vk::Extent2D extent, vk::Format imageFormat, cstr name)
//{
// vk::ImageCreateInfo imageCreateInfo = {
// .imageType = vk::ImageType::e2D,
// .format = imageFormat,
// .extent = ToExtent3D(extent, 1),
// .mipLevels = 1,
// .arrayLayers = 1,
// .samples = vk::SampleCountFlagBits::e1,
// .tiling = vk::ImageTiling::eOptimal,
// .usage = vk::ImageUsageFlagBits::eColorAttachment | vk::ImageUsageFlagBits::eTransferSrc,
// .sharingMode = vk::SharingMode::eExclusive,
// .initialLayout = vk::ImageLayout::eUndefined,
// };
// constexpr VmaAllocationCreateInfo allocationCreateInfo = {
// .flags = VMA_ALLOCATION_CREATE_DEDICATED_MEMORY_BIT,
// .usage = VMA_MEMORY_USAGE_AUTO,
// };
//
// VkImage image;
// VmaAllocation allocation;
// auto result = static_cast<vk::Result>(vmaCreateImage(device->m_Allocator, reinterpret_cast<VkImageCreateInfo
// *>(&imageCreateInfo),
// &allocationCreateInfo, &image, &allocation, nullptr));
// ERROR_IF(Failed(result), "Could not allocate depth buffer. Cause: {}", result) THEN_ABORT(result);
//
// vk::ImageView view;
// vk::ImageViewCreateInfo imageViewCreateInfo = {
// .image = image,
// .viewType = vk::ImageViewType::e2D,
// .format = imageFormat,
// .components = {},
// .subresourceRange =
// {
// .aspectMask = vk::ImageAspectFlagBits::eColor,
// .baseMipLevel = 0,
// .levelCount = 1,
// .baseArrayLayer = 0,
// .layerCount = 1,
// },
// };
// result = device->m_Device.createImageView(&imageViewCreateInfo, nullptr, &view);
// ERROR_IF(Failed(result), "Could not create attachment image view {}. Cause: {}", name, result)
// THEN_ABORT(result);
//
// m_Device = device;
// m_Image = image;
// m_View = view;
// m_Allocation = allocation;
// m_Extent = imageCreateInfo.extent;
// m_MipLevels = 1;
// m_LayerCount = 1;
//
// device->SetName(m_Image, name);
// }
//
// void
// DepthImage::Init(const Device *device, vk::Extent2D extent, cstr name)
//{
// constexpr vk::Format imageFormat = vk::Format::eD24UnormS8Uint;
// vk::ImageCreateInfo imageCreateInfo = {
// .imageType = vk::ImageType::e2D,
// .format = imageFormat,
// .extent = ToExtent3D(extent, 1),
// .mipLevels = 1,
// .arrayLayers = 1,
// .samples = vk::SampleCountFlagBits::e1,
// .tiling = vk::ImageTiling::eOptimal,
// .usage = vk::ImageUsageFlagBits::eDepthStencilAttachment,
// .sharingMode = vk::SharingMode::eExclusive,
// .initialLayout = vk::ImageLayout::eUndefined,
// };
// constexpr VmaAllocationCreateInfo allocationCreateInfo = {
// .flags = VMA_ALLOCATION_CREATE_DEDICATED_MEMORY_BIT,
// .usage = VMA_MEMORY_USAGE_AUTO,
// };
//
// VkImage image;
// VmaAllocation allocation;
// auto result = static_cast<vk::Result>(vmaCreateImage(device->m_Allocator, reinterpret_cast<VkImageCreateInfo
// *>(&imageCreateInfo),
// &allocationCreateInfo, &image, &allocation, nullptr));
// ERROR_IF(Failed(result), "Could not allocate depth buffer. Cause: {}", result) THEN_ABORT(result);
//
// vk::ImageView view;
// vk::ImageViewCreateInfo imageViewCreateInfo = {
// .image = image,
// .viewType = vk::ImageViewType::e2D,
// .format = imageFormat,
// .components = {},
// .subresourceRange =
// {
// .aspectMask = vk::ImageAspectFlagBits::eDepth,
// .baseMipLevel = 0,
// .levelCount = 1,
// .baseArrayLayer = 0,
// .layerCount = 1,
// },
// };
// result = device->m_Device.createImageView(&imageViewCreateInfo, nullptr, &view);
// ERROR_IF(Failed(result), "Could not create depth image view {}. Cause: {}", name, result) THEN_ABORT(result);
//
// m_Device = device;
// m_Image = image;
// m_View = view;
// m_Allocation = allocation;
// m_Extent = imageCreateInfo.extent;
// m_MipLevels = 1;
// m_LayerCount = 1;
//
// device->SetName(m_Image, name);
// }
//
// void
// StorageTexture::Init(const Device *device, vk::Extent2D extent, const vk::Format imageFormat, const bool isSampled,
// cstr name)
//{
// // Reasoning:
// // Transfer Src and Dst to copy to and from the buffer since Storage will often be loaded with info, and read for
// // results.
// auto usage =
// vk::ImageUsageFlagBits::eStorage | vk::ImageUsageFlagBits::eTransferSrc |
// vk::ImageUsageFlagBits::eTransferDst;
// if (isSampled)
// {
// WARN_IF(!IsPowerOfTwo(extent.width) || !IsPowerOfTwo(extent.width), "Image {2} is {0}x{1} (Non Power of
// Two)",
// extent.width, extent.height, name ? name : "<unnamed>");
// usage |= vk::ImageUsageFlagBits::eSampled;
// }
//
// vk::ImageCreateInfo imageCreateInfo = {
// .imageType = vk::ImageType::e2D,
// .format = imageFormat,
// .extent = ToExtent3D(extent, 1),
// .mipLevels = 1,
// .arrayLayers = 1,
// .samples = vk::SampleCountFlagBits::e1,
// .tiling = vk::ImageTiling::eOptimal,
// .usage = usage,
// .sharingMode = vk::SharingMode::eExclusive,
// .initialLayout = vk::ImageLayout::eUndefined,
// };
// constexpr VmaAllocationCreateInfo allocationCreateInfo = {
// .flags = {},
// .usage = VMA_MEMORY_USAGE_AUTO,
// };
//
// VkImage image;
// VmaAllocation allocation;
// auto result = static_cast<vk::Result>(vmaCreateImage(device->m_Allocator, reinterpret_cast<VkImageCreateInfo
// *>(&imageCreateInfo),
// &allocationCreateInfo, &image, &allocation, nullptr));
// ERROR_IF(Failed(result), "Could not allocate image {}. Cause: {}", name, result) THEN_ABORT(result);
//
// vk::ImageView view;
// const vk::ImageViewCreateInfo imageViewCreateInfo = {
// .image = image,
// .viewType = vk::ImageViewType::e2D,
// .format = imageFormat,
// .components = {},
// .subresourceRange =
// {
// .aspectMask = vk::ImageAspectFlagBits::eColor,
// .baseMipLevel = 0,
// .levelCount = 1,
// .baseArrayLayer = 0,
// .layerCount = 1,
// },
// };
// result = device->m_Device.createImageView(&imageViewCreateInfo, nullptr, &view);
// ERROR_IF(Failed(result), "Could not create image view {}. Cause: {}", name, result) THEN_ABORT(result);
//
// m_Device = device;
// m_Image = image;
// m_View = view;
// m_Allocation = allocation;
// m_Extent = imageCreateInfo.extent;
// m_MipLevels = 1;
// m_LayerCount = 1;
//
// device->SetName(m_Image, name);
// }
//
// void
// StorageTextureCube::Init(const Device *device, u32 cubeSide, vk::Format imageFormat, bool isSampled, bool
// isMipMapped,
// cstr name)
//{
// // Reasoning:
// // Transfer Src and Dst to copy to and from the buffer since Storage will often be loaded with info, and read for
// // results.
// auto usage =
// vk::ImageUsageFlagBits::eStorage | vk::ImageUsageFlagBits::eTransferSrc |
// vk::ImageUsageFlagBits::eTransferDst;
// if (isSampled)
// {
// WARN_IF(!IsPowerOfTwo(cubeSide), "Image {1} is {0}x{0} (Non Power of Two)", cubeSide,
// name ? name : "<unnamed>");
// usage |= vk::ImageUsageFlagBits::eSampled;
// }
//
// const u8 mipLevels = isMipMapped ? 1 + static_cast<u8>(floor(log2(cubeSide))) : 1;
//
// vk::ImageCreateInfo imageCreateInfo = {
// .flags = vk::ImageCreateFlagBits::eCubeCompatible,
// .imageType = vk::ImageType::e2D,
// .format = imageFormat,
// .extent = {cubeSide, cubeSide, 1},
// .mipLevels = mipLevels,
// .arrayLayers = 6,
// .samples = vk::SampleCountFlagBits::e1,
// .tiling = vk::ImageTiling::eOptimal,
// .usage = usage,
// .sharingMode = vk::SharingMode::eExclusive,
// .initialLayout = vk::ImageLayout::eUndefined,
// };
// constexpr VmaAllocationCreateInfo allocationCreateInfo = {
// .flags = {},
// .usage = VMA_MEMORY_USAGE_AUTO,
// };
//
// VkImage image;
// VmaAllocation allocation;
// auto result = static_cast<vk::Result>(vmaCreateImage(device->m_Allocator, reinterpret_cast<VkImageCreateInfo
// *>(&imageCreateInfo),
// &allocationCreateInfo, &image, &allocation, nullptr));
// ERROR_IF(Failed(result), "Could not allocate image {}. Cause: {}", name, result) THEN_ABORT(result);
//
// vk::ImageView view;
// const vk::ImageViewCreateInfo imageViewCreateInfo = {
// .image = image,
// .viewType = vk::ImageViewType::eCube,
// .format = imageFormat,
// .components = {},
// .subresourceRange =
// {
// .aspectMask = vk::ImageAspectFlagBits::eColor,
// .baseMipLevel = 0,
// .levelCount = mipLevels,
// .baseArrayLayer = 0,
// .layerCount = 6,
// },
// };
// result = device->m_Device.createImageView(&imageViewCreateInfo, nullptr, &view);
// ERROR_IF(Failed(result), "Could not create image view {}. Cause: {}", name, result) THEN_ABORT(result);
//
// m_Device = device;
// m_Image = image;
// m_View = view;
// m_Allocation = allocation;
// m_Extent = imageCreateInfo.extent;
// m_MipLevels = mipLevels;
// m_LayerCount = 6;
//
// device->SetName(m_Image, name);
// }
Image::Image(Image &&other) noexcept
: m_Device{Take(other.m_Device)}
, m_Image{Take(other.m_Image)}
, m_Allocation{Take(other.m_Allocation)}
, m_Extent{other.m_Extent}
, m_Format{other.m_Format}
, m_EmptyPadding_{other.m_EmptyPadding_}
, m_Flags_{other.m_Flags_}
, m_LayerCount{other.m_LayerCount}
, m_MipLevels{other.m_MipLevels}
{ {
WARN_IF(!IsPowerOfTwo(cubeSide), "Image Cube {1} has side {0}x{0} (Non Power of Two)", cubeSide, name ? name : "<unnamed>");
const u8 mipLevels = isMipMapped ? 1 + Cast<u8>(floor(log2(cubeSide))) : 1;
auto usage = vk::ImageUsageFlagBits::eSampled | vk::ImageUsageFlagBits::eTransferDst;
if (isMipMapped)
{
usage |= vk::ImageUsageFlagBits::eTransferSrc;
}
const vk::Extent3D extent = {.width = cubeSide, .height = cubeSide, .depth = 1};
vk::ImageCreateInfo imageCreateInfo = {
.flags = vk::ImageCreateFlagBits::eCubeCompatible,
.imageType = vk::ImageType::e2D,
.format = imageFormat,
.extent = extent,
.mipLevels = mipLevels,
.arrayLayers = 6,
.samples = vk::SampleCountFlagBits::e1,
.tiling = vk::ImageTiling::eOptimal,
.usage = usage,
.sharingMode = vk::SharingMode::eExclusive,
.initialLayout = vk::ImageLayout::eUndefined,
};
constexpr VmaAllocationCreateInfo allocationCreateInfo = {
.flags = {},
.usage = VMA_MEMORY_USAGE_AUTO,
};
VkImage image;
VmaAllocation allocation;
auto result = Cast<vk::Result>(vmaCreateImage(device->m_Allocator, Recast<VkImageCreateInfo *>(&imageCreateInfo),
&allocationCreateInfo, &image, &allocation, nullptr));
ERROR_IF(Failed(result), "Could not allocate image {}. Cause: {}", name, result) THEN_ABORT(result);
vk::ImageView view;
vk::ImageViewCreateInfo imageViewCreateInfo = {
.image = image,
.viewType = vk::ImageViewType::eCube,
.format = imageFormat,
.components = {},
.subresourceRange =
{
.aspectMask = vk::ImageAspectFlagBits::eColor,
.baseMipLevel = 0,
.levelCount = mipLevels,
.baseArrayLayer = 0,
.layerCount = 6,
},
};
result = device->m_Device.createImageView(&imageViewCreateInfo, nullptr, &view);
ERROR_IF(Failed(result), "Could not create image view {}. Cause: {}", name, result) THEN_ABORT(result);
m_Image = image;
m_View = view;
m_Allocation = allocation;
m_Extent = extent;
m_MipLevels = mipLevels;
m_Flags_ = OWNED_BIT | VALID_BIT;
m_LayerCount = 6;
device->SetName(m_Image, name);
} }
void Image::Image(Device const *device, vk::Image const image, VmaAllocation const allocation, vk::Extent3D const extent,
AttachmentImage::Init(const Device *device, vk::Extent2D extent, vk::Format imageFormat, cstr name) vk::Format const format, Flags const flags, u8 const layerCount, u8 const mipLevels)
: m_Device{device}
, m_Image{image}
, m_Allocation{allocation}
, m_Extent{extent}
, m_Format{format}
, m_Flags_{flags}
, m_LayerCount{layerCount}
, m_MipLevels{mipLevels}
{ {
vk::ImageCreateInfo imageCreateInfo = { }
.imageType = vk::ImageType::e2D,
.format = imageFormat,
.extent = ToExtent3D(extent, 1),
.mipLevels = 1,
.arrayLayers = 1,
.samples = vk::SampleCountFlagBits::e1,
.tiling = vk::ImageTiling::eOptimal,
.usage = vk::ImageUsageFlagBits::eColorAttachment | vk::ImageUsageFlagBits::eTransferSrc,
.sharingMode = vk::SharingMode::eExclusive,
.initialLayout = vk::ImageLayout::eUndefined,
};
constexpr VmaAllocationCreateInfo allocationCreateInfo = {
.flags = VMA_ALLOCATION_CREATE_DEDICATED_MEMORY_BIT,
.usage = VMA_MEMORY_USAGE_AUTO,
};
VkImage image;
VmaAllocation allocation;
auto result = Cast<vk::Result>(vmaCreateImage(device->m_Allocator, Recast<VkImageCreateInfo *>(&imageCreateInfo),
&allocationCreateInfo, &image, &allocation, nullptr));
ERROR_IF(Failed(result), "Could not allocate depth buffer. Cause: {}", result) THEN_ABORT(result);
vk::ImageView view;
vk::ImageViewCreateInfo imageViewCreateInfo = {
.image = image,
.viewType = vk::ImageViewType::e2D,
.format = imageFormat,
.components = {},
.subresourceRange =
{
.aspectMask = vk::ImageAspectFlagBits::eColor,
.baseMipLevel = 0,
.levelCount = 1,
.baseArrayLayer = 0,
.layerCount = 1,
},
};
result = device->m_Device.createImageView(&imageViewCreateInfo, nullptr, &view);
ERROR_IF(Failed(result), "Could not create attachment image view {}. Cause: {}", name, result) THEN_ABORT(result);
m_Image = image;
m_View = view;
m_Allocation = allocation;
m_Extent = imageCreateInfo.extent;
m_MipLevels = 1;
m_Flags_ = OWNED_BIT | VALID_BIT;
m_LayerCount = 1;
device->SetName(m_Image, name);
}
void
DepthImage::Init(const Device *device, vk::Extent2D extent, cstr name)
{
constexpr vk::Format imageFormat = vk::Format::eD24UnormS8Uint;
vk::ImageCreateInfo imageCreateInfo = {
.imageType = vk::ImageType::e2D,
.format = imageFormat,
.extent = ToExtent3D(extent, 1),
.mipLevels = 1,
.arrayLayers = 1,
.samples = vk::SampleCountFlagBits::e1,
.tiling = vk::ImageTiling::eOptimal,
.usage = vk::ImageUsageFlagBits::eDepthStencilAttachment,
.sharingMode = vk::SharingMode::eExclusive,
.initialLayout = vk::ImageLayout::eUndefined,
};
constexpr VmaAllocationCreateInfo allocationCreateInfo = {
.flags = VMA_ALLOCATION_CREATE_DEDICATED_MEMORY_BIT,
.usage = VMA_MEMORY_USAGE_AUTO,
};
VkImage image;
VmaAllocation allocation;
auto result = Cast<vk::Result>(vmaCreateImage(device->m_Allocator, Recast<VkImageCreateInfo *>(&imageCreateInfo),
&allocationCreateInfo, &image, &allocation, nullptr));
ERROR_IF(Failed(result), "Could not allocate depth buffer. Cause: {}", result) THEN_ABORT(result);
vk::ImageView view;
vk::ImageViewCreateInfo imageViewCreateInfo = {
.image = image,
.viewType = vk::ImageViewType::e2D,
.format = imageFormat,
.components = {},
.subresourceRange =
{
.aspectMask = vk::ImageAspectFlagBits::eDepth,
.baseMipLevel = 0,
.levelCount = 1,
.baseArrayLayer = 0,
.layerCount = 1,
},
};
result = device->m_Device.createImageView(&imageViewCreateInfo, nullptr, &view);
ERROR_IF(Failed(result), "Could not create depth image view {}. Cause: {}", name, result) THEN_ABORT(result);
m_Image = image;
m_View = view;
m_Allocation = allocation;
m_Extent = imageCreateInfo.extent;
m_MipLevels = 1;
m_Flags_ = OWNED_BIT | VALID_BIT;
m_LayerCount = 1;
device->SetName(m_Image, name);
}
void
StorageTexture::Init(const Device *device, vk::Extent2D extent, const vk::Format imageFormat, const bool isSampled,
cstr name)
{
// Reasoning:
// Transfer Src and Dst to copy to and from the buffer since Storage will often be loaded with info, and read for
// results.
auto usage =
vk::ImageUsageFlagBits::eStorage | vk::ImageUsageFlagBits::eTransferSrc | vk::ImageUsageFlagBits::eTransferDst;
if (isSampled)
{
WARN_IF(!IsPowerOfTwo(extent.width) || !IsPowerOfTwo(extent.width), "Image {2} is {0}x{1} (Non Power of Two)",
extent.width, extent.height, name ? name : "<unnamed>");
usage |= vk::ImageUsageFlagBits::eSampled;
}
vk::ImageCreateInfo imageCreateInfo = {
.imageType = vk::ImageType::e2D,
.format = imageFormat,
.extent = ToExtent3D(extent, 1),
.mipLevels = 1,
.arrayLayers = 1,
.samples = vk::SampleCountFlagBits::e1,
.tiling = vk::ImageTiling::eOptimal,
.usage = usage,
.sharingMode = vk::SharingMode::eExclusive,
.initialLayout = vk::ImageLayout::eUndefined,
};
constexpr VmaAllocationCreateInfo allocationCreateInfo = {
.flags = {},
.usage = VMA_MEMORY_USAGE_AUTO,
};
VkImage image;
VmaAllocation allocation;
auto result = Cast<vk::Result>(vmaCreateImage(device->m_Allocator, Recast<VkImageCreateInfo *>(&imageCreateInfo),
&allocationCreateInfo, &image, &allocation, nullptr));
ERROR_IF(Failed(result), "Could not allocate image {}. Cause: {}", name, result) THEN_ABORT(result);
vk::ImageView view;
const vk::ImageViewCreateInfo imageViewCreateInfo = {
.image = image,
.viewType = vk::ImageViewType::e2D,
.format = imageFormat,
.components = {},
.subresourceRange =
{
.aspectMask = vk::ImageAspectFlagBits::eColor,
.baseMipLevel = 0,
.levelCount = 1,
.baseArrayLayer = 0,
.layerCount = 1,
},
};
result = device->m_Device.createImageView(&imageViewCreateInfo, nullptr, &view);
ERROR_IF(Failed(result), "Could not create image view {}. Cause: {}", name, result) THEN_ABORT(result);
m_Image = image;
m_View = view;
m_Allocation = allocation;
m_Extent = imageCreateInfo.extent;
m_MipLevels = 1;
m_Flags_ = OWNED_BIT | VALID_BIT;
m_LayerCount = 1;
device->SetName(m_Image, name);
}
void
StorageTextureCube::Init(const Device *device, u32 cubeSide, vk::Format imageFormat, bool isSampled, bool isMipMapped,
cstr name)
{
// Reasoning:
// Transfer Src and Dst to copy to and from the buffer since Storage will often be loaded with info, and read for
// results.
auto usage =
vk::ImageUsageFlagBits::eStorage | vk::ImageUsageFlagBits::eTransferSrc | vk::ImageUsageFlagBits::eTransferDst;
if (isSampled)
{
WARN_IF(!IsPowerOfTwo(cubeSide), "Image {1} is {0}x{0} (Non Power of Two)", cubeSide,
name ? name : "<unnamed>");
usage |= vk::ImageUsageFlagBits::eSampled;
}
const u8 mipLevels = isMipMapped ? 1 + Cast<u8>(floor(log2(cubeSide))) : 1;
vk::ImageCreateInfo imageCreateInfo = {
.flags = vk::ImageCreateFlagBits::eCubeCompatible,
.imageType = vk::ImageType::e2D,
.format = imageFormat,
.extent = {cubeSide, cubeSide, 1},
.mipLevels = mipLevels,
.arrayLayers = 6,
.samples = vk::SampleCountFlagBits::e1,
.tiling = vk::ImageTiling::eOptimal,
.usage = usage,
.sharingMode = vk::SharingMode::eExclusive,
.initialLayout = vk::ImageLayout::eUndefined,
};
constexpr VmaAllocationCreateInfo allocationCreateInfo = {
.flags = {},
.usage = VMA_MEMORY_USAGE_AUTO,
};
VkImage image;
VmaAllocation allocation;
auto result = Cast<vk::Result>(vmaCreateImage(device->m_Allocator, Recast<VkImageCreateInfo *>(&imageCreateInfo),
&allocationCreateInfo, &image, &allocation, nullptr));
ERROR_IF(Failed(result), "Could not allocate image {}. Cause: {}", name, result) THEN_ABORT(result);
vk::ImageView view;
const vk::ImageViewCreateInfo imageViewCreateInfo = {
.image = image,
.viewType = vk::ImageViewType::eCube,
.format = imageFormat,
.components = {},
.subresourceRange =
{
.aspectMask = vk::ImageAspectFlagBits::eColor,
.baseMipLevel = 0,
.levelCount = mipLevels,
.baseArrayLayer = 0,
.layerCount = 6,
},
};
result = device->m_Device.createImageView(&imageViewCreateInfo, nullptr, &view);
ERROR_IF(Failed(result), "Could not create image view {}. Cause: {}", name, result) THEN_ABORT(result);
m_Image = image;
m_View = view;
m_Allocation = allocation;
m_Extent = imageCreateInfo.extent;
m_MipLevels = mipLevels;
m_Flags_ = OWNED_BIT | VALID_BIT;
m_LayerCount = 6;
device->SetName(m_Image, name);
}

View File

@ -1,56 +1,56 @@
// ============================================= // =============================================
// Aster: context.cpp // Aster: context.cpp
// Copyright (c) 2020-2024 Anish Bhobe // Copyright (c) 2020-2025 Anish Bhobe
// ============================================= // =============================================
#include "core/context.h" #include "aster/core/instance.h"
#include "aster/core/window.h"
#include <EASTL/array.h> #include <EASTL/array.h>
#include <EASTL/fixed_vector.h> #include <EASTL/fixed_vector.h>
using namespace aster;
VKAPI_ATTR b32 VKAPI_CALL VKAPI_ATTR b32 VKAPI_CALL
DebugCallback(const VkDebugUtilsMessageSeverityFlagBitsEXT messageSeverity, DebugCallback(vk::DebugUtilsMessageSeverityFlagBitsEXT const messageSeverity,
const VkDebugUtilsMessageTypeFlagsEXT messageType, vk::DebugUtilsMessageTypeFlagsEXT const messageType,
const VkDebugUtilsMessengerCallbackDataEXT *callbackData, [[maybe_unused]] void *userData) vk::DebugUtilsMessengerCallbackDataEXT const *callbackData, [[maybe_unused]] void *userData)
{ {
using Severity = vk::DebugUtilsMessageSeverityFlagsEXT;
using SeverityBits = vk::DebugUtilsMessageSeverityFlagBitsEXT; using SeverityBits = vk::DebugUtilsMessageSeverityFlagBitsEXT;
using MessageType = vk::DebugUtilsMessageTypeFlagsEXT;
using MessageTypeBits = vk::DebugUtilsMessageTypeFlagBitsEXT; using MessageTypeBits = vk::DebugUtilsMessageTypeFlagBitsEXT;
const auto severity = Severity(messageSeverity); if (messageType & MessageTypeBits::eValidation)
if (MessageType(messageType) & MessageTypeBits::eValidation)
{ {
if (severity & SeverityBits::eError) if (messageSeverity & SeverityBits::eError)
ERROR("{}", callbackData->pMessage); ERROR("{}", callbackData->pMessage);
if (severity & SeverityBits::eWarning) if (messageSeverity & SeverityBits::eWarning)
WARN("{}", callbackData->pMessage); WARN("{}", callbackData->pMessage);
if (severity & SeverityBits::eInfo) if (messageSeverity & SeverityBits::eInfo)
INFO("{}", callbackData->pMessage); INFO("{}", callbackData->pMessage);
if (severity & SeverityBits::eVerbose) if (messageSeverity & SeverityBits::eVerbose)
VERBOSE("{}", callbackData->pMessage); VERBOSE("{}", callbackData->pMessage);
} }
return false; return false;
} }
Context::Context(const cstr appName, const Version version, bool enableValidation) Instance::Instance(cstr const appName, Version const version, bool enableValidation)
{ {
INFO_IF(enableValidation, "Validation Layers enabled"); INFO_IF(enableValidation, "Validation Layers enabled");
// TODO Get/Check API Version // TODO Get/Check API Version
// Creating Instance // Creating Instance
const vk::ApplicationInfo appInfo = { vk::ApplicationInfo const appInfo = {
.pApplicationName = appName, .pApplicationName = appName,
.applicationVersion = version.GetVkVersion(), .applicationVersion = version.GetVkVersion(),
.pEngineName = PROJECT_NAME, .pEngineName = "aster",
.engineVersion = version.GetVkVersion(), .engineVersion = version.GetVkVersion(),
.apiVersion = ASTER_API_VERSION, .apiVersion = ASTER_API_VERSION,
}; };
const vk::DebugUtilsMessengerCreateInfoEXT debugUtilsMessengerCreateInfo = { vk::DebugUtilsMessengerCreateInfoEXT const debugUtilsMessengerCreateInfo = {
.messageSeverity = vk::DebugUtilsMessageSeverityFlagBitsEXT::eError | .messageSeverity = vk::DebugUtilsMessageSeverityFlagBitsEXT::eError |
vk::DebugUtilsMessageSeverityFlagBitsEXT::eWarning | vk::DebugUtilsMessageSeverityFlagBitsEXT::eWarning |
vk::DebugUtilsMessageSeverityFlagBitsEXT::eInfo, vk::DebugUtilsMessageSeverityFlagBitsEXT::eInfo,
@ -61,23 +61,23 @@ Context::Context(const cstr appName, const Version version, bool enableValidatio
.pUserData = nullptr, .pUserData = nullptr,
}; };
u32 glfwExtensionCount = 0; u32 windowExtensionCount = 0;
cstr *glfwExtensions = glfwGetRequiredInstanceExtensions(&glfwExtensionCount); cstr *windowExtensions = Window::GetInstanceExtensions(&windowExtensionCount);
eastl::fixed_vector<cstr, 3> instanceExtensions(glfwExtensions, glfwExtensions + glfwExtensionCount); eastl::fixed_vector<cstr, 3> instanceExtensions(windowExtensions, windowExtensions + windowExtensionCount);
if (enableValidation) if (enableValidation)
{ {
instanceExtensions.push_back(VK_EXT_DEBUG_UTILS_EXTENSION_NAME); instanceExtensions.push_back(VK_EXT_DEBUG_UTILS_EXTENSION_NAME);
} }
const vk::DynamicLoader dl; vk::detail::DynamicLoader const dl;
// ReSharper disable once CppInconsistentNaming // ReSharper disable once CppInconsistentNaming
const auto vkGetInstanceProcAddr = dl.getProcAddress<PFN_vkGetInstanceProcAddr>("vkGetInstanceProcAddr"); auto const vkGetInstanceProcAddr = dl.getProcAddress<PFN_vkGetInstanceProcAddr>("vkGetInstanceProcAddr");
VULKAN_HPP_DEFAULT_DISPATCHER.init(vkGetInstanceProcAddr); VULKAN_HPP_DEFAULT_DISPATCHER.init(vkGetInstanceProcAddr);
const auto instanceCreateInfo = vk::InstanceCreateInfo{ auto const instanceCreateInfo = vk::InstanceCreateInfo{
.pNext = enableValidation ? &debugUtilsMessengerCreateInfo : nullptr, .pNext = enableValidation ? &debugUtilsMessengerCreateInfo : nullptr,
.pApplicationInfo = &appInfo, .pApplicationInfo = &appInfo,
.enabledExtensionCount = Cast<u32>(instanceExtensions.size()), .enabledExtensionCount = static_cast<u32>(instanceExtensions.size()),
.ppEnabledExtensionNames = instanceExtensions.data(), .ppEnabledExtensionNames = instanceExtensions.data(),
}; };
@ -97,8 +97,11 @@ Context::Context(const cstr appName, const Version version, bool enableValidatio
} }
} }
Context::~Context() Instance::~Instance()
{ {
if (!m_Instance)
return;
if (m_DebugMessenger) if (m_DebugMessenger)
{ {
m_Instance.destroy(m_DebugMessenger, nullptr); m_Instance.destroy(m_DebugMessenger, nullptr);
@ -108,18 +111,18 @@ Context::~Context()
DEBUG("Instance destroyed"); DEBUG("Instance destroyed");
} }
Context::Context(Context &&other) noexcept Instance::Instance(Instance &&other) noexcept
: m_Instance(Take(other.m_Instance)) : m_Instance(Take(other.m_Instance))
, m_DebugMessenger(Take(other.m_DebugMessenger)) , m_DebugMessenger(Take(other.m_DebugMessenger))
{ {
} }
Context & Instance &
Context::operator=(Context &&other) noexcept Instance::operator=(Instance &&other) noexcept
{ {
if (this == &other) if (this == &other)
return *this; return *this;
m_Instance = Take(other.m_Instance); m_Instance = Take(other.m_Instance);
m_DebugMessenger = Take(other.m_DebugMessenger); m_DebugMessenger = Take(other.m_DebugMessenger);
return *this; return *this;
} }

View File

@ -1,15 +1,17 @@
// ============================================= // =============================================
// Aster: physical_device.cpp // Aster: physical_device.cpp
// Copyright (c) 2020-2024 Anish Bhobe // Copyright (c) 2020-2025 Anish Bhobe
// ============================================= // =============================================
#include "core/physical_device.h" #include "aster/core/physical_device.h"
#include "core/context.h" #include "aster/core/instance.h"
#include "core/surface.h" #include "aster/core/surface.h"
using namespace aster;
[[nodiscard]] vk::SurfaceCapabilitiesKHR [[nodiscard]] vk::SurfaceCapabilitiesKHR
GetSurfaceCapabilities(const vk::PhysicalDevice physicalDevice, const vk::SurfaceKHR surface) aster::GetSurfaceCapabilities(vk::PhysicalDevice const physicalDevice, vk::SurfaceKHR const surface)
{ {
vk::SurfaceCapabilitiesKHR surfaceCapabilities; vk::SurfaceCapabilitiesKHR surfaceCapabilities;
@ -21,7 +23,7 @@ GetSurfaceCapabilities(const vk::PhysicalDevice physicalDevice, const vk::Surfac
} }
[[nodiscard]] eastl::vector<vk::SurfaceFormatKHR> [[nodiscard]] eastl::vector<vk::SurfaceFormatKHR>
GetSurfaceFormats(const vk::PhysicalDevice physicalDevice, const vk::SurfaceKHR surface) aster::GetSurfaceFormats(vk::PhysicalDevice const physicalDevice, vk::SurfaceKHR const surface)
{ {
// vk::Result::eIncomplete should not occur in this function. The rest are errors. Thus, abort is allowed. // vk::Result::eIncomplete should not occur in this function. The rest are errors. Thus, abort is allowed.
u32 count = 0; u32 count = 0;
@ -38,7 +40,7 @@ GetSurfaceFormats(const vk::PhysicalDevice physicalDevice, const vk::SurfaceKHR
} }
[[nodiscard]] eastl::vector<vk::PresentModeKHR> [[nodiscard]] eastl::vector<vk::PresentModeKHR>
GetSurfacePresentModes(const vk::PhysicalDevice physicalDevice, const vk::SurfaceKHR surface) aster::GetSurfacePresentModes(vk::PhysicalDevice const physicalDevice, vk::SurfaceKHR const surface)
{ {
// vk::Result::eIncomplete should not occur in this function. The rest are errors. Thus, abort is allowed. // vk::Result::eIncomplete should not occur in this function. The rest are errors. Thus, abort is allowed.
u32 count = 0; u32 count = 0;
@ -55,11 +57,11 @@ GetSurfacePresentModes(const vk::PhysicalDevice physicalDevice, const vk::Surfac
} }
[[nodiscard]] bool [[nodiscard]] bool
GetQueuePresentSupport(const u32 queueFamilyIndex, const vk::SurfaceKHR surface, GetQueuePresentSupport(u32 const queueFamilyIndex, vk::SurfaceKHR const surface,
const vk::PhysicalDevice physicalDevice) vk::PhysicalDevice const physicalDevice)
{ {
b32 supported = false; b32 supported = false;
const vk::Result result = physicalDevice.getSurfaceSupportKHR(queueFamilyIndex, surface, &supported); vk::Result const result = physicalDevice.getSurfaceSupportKHR(queueFamilyIndex, surface, &supported);
ERROR_IF(Failed(result), "Could not get queue family surface support. Cause: {}", result) ERROR_IF(Failed(result), "Could not get queue family surface support. Cause: {}", result)
THEN_ABORT(result); THEN_ABORT(result);
@ -67,7 +69,7 @@ GetQueuePresentSupport(const u32 queueFamilyIndex, const vk::SurfaceKHR surface,
} }
[[nodiscard]] eastl::fixed_vector<vk::QueueFamilyProperties, 32> [[nodiscard]] eastl::fixed_vector<vk::QueueFamilyProperties, 32>
GetQueueFamilyProperties(const vk::PhysicalDevice physicalDevice) GetQueueFamilyProperties(vk::PhysicalDevice const physicalDevice)
{ {
// Devices rarely have more than 32 queue families. Thus fixed vector // Devices rarely have more than 32 queue families. Thus fixed vector
u32 count = 0; u32 count = 0;
@ -81,7 +83,7 @@ GetQueueFamilyProperties(const vk::PhysicalDevice physicalDevice)
// Size 384 return. // Size 384 return.
[[nodiscard]] eastl::vector<QueueFamilyInfo> [[nodiscard]] eastl::vector<QueueFamilyInfo>
GetQueueFamilies(const vk::SurfaceKHR surface, const vk::PhysicalDevice physicalDevice) GetQueueFamilies(vk::SurfaceKHR const surface, vk::PhysicalDevice const physicalDevice)
{ {
auto queueFamilyProperties = GetQueueFamilyProperties(physicalDevice); auto queueFamilyProperties = GetQueueFamilyProperties(physicalDevice);
@ -126,7 +128,7 @@ GetQueueFamilies(const vk::SurfaceKHR surface, const vk::PhysicalDevice physical
return queueFamilyInfos; return queueFamilyInfos;
} }
PhysicalDevice::PhysicalDevice(const vk::SurfaceKHR surface, const vk::PhysicalDevice physicalDevice) PhysicalDevice::PhysicalDevice(vk::SurfaceKHR const surface, vk::PhysicalDevice const physicalDevice)
{ {
physicalDevice.getProperties(&m_DeviceProperties); physicalDevice.getProperties(&m_DeviceProperties);
physicalDevice.getFeatures(&m_DeviceFeatures); physicalDevice.getFeatures(&m_DeviceFeatures);
@ -139,7 +141,7 @@ PhysicalDevice::PhysicalDevice(const vk::SurfaceKHR surface, const vk::PhysicalD
} }
eastl::fixed_vector<vk::PhysicalDevice, 8> eastl::fixed_vector<vk::PhysicalDevice, 8>
EnumeratePhysicalDevices(const vk::Instance instance) EnumeratePhysicalDevices(vk::Instance const instance)
{ {
u32 count = 0; u32 count = 0;
vk::Result result = instance.enumeratePhysicalDevices(&count, nullptr); vk::Result result = instance.enumeratePhysicalDevices(&count, nullptr);
@ -154,11 +156,10 @@ EnumeratePhysicalDevices(const vk::Instance instance)
return physicalDevices; return physicalDevices;
} }
PhysicalDevices::PhysicalDevices(const Surface *surface, const Context *context) PhysicalDevices::PhysicalDevices(Surface const &surface, Instance const &context)
{ {
auto physicalDevices = EnumeratePhysicalDevices(context->m_Instance); for (auto physicalDevices = EnumeratePhysicalDevices(context.m_Instance); auto physicalDevice : physicalDevices)
for (auto physicalDevice : physicalDevices)
{ {
this->emplace_back(surface->m_Surface, physicalDevice); this->emplace_back(surface.m_Surface, physicalDevice);
} }
} }

View File

@ -1,24 +1,30 @@
// ============================================= // =============================================
// Aster: pipeline.cpp // Aster: pipeline.cpp
// Copyright (c) 2020-2024 Anish Bhobe // Copyright (c) 2020-2025 Anish Bhobe
// ============================================= // =============================================
#include "core/pipeline.h" #include "aster/core/pipeline.h"
#include "core/device.h" #include "aster/core/device.h"
Pipeline::Pipeline(const Device *device, vk::PipelineLayout layout, vk::Pipeline pipeline, using namespace aster;
eastl::vector<vk::DescriptorSetLayout> &&setLayouts)
: m_Device(device) Pipeline::Pipeline(Device const *device, vk::PipelineLayout const layout, vk::Pipeline const pipeline,
, m_Layout(layout) eastl::vector<vk::DescriptorSetLayout> &&setLayouts, Kind const kind)
, m_Pipeline(pipeline) : m_Device{device}
, m_SetLayouts(std::move(setLayouts)) , m_Layout{layout}
, m_Pipeline{pipeline}
, m_SetLayouts{std::move(setLayouts)}
, m_Kind{kind}
{ {
} }
Pipeline::~Pipeline() Pipeline::~Pipeline()
{ {
for (const auto setLayout : m_SetLayouts) if (!m_Device || !m_Pipeline)
return;
for (auto const setLayout : m_SetLayouts)
{ {
m_Device->m_Device.destroy(setLayout, nullptr); m_Device->m_Device.destroy(setLayout, nullptr);
} }

View File

@ -0,0 +1,42 @@
// =============================================
// Aster: sampler.cpp
// Copyright (c) 2020-2025 Anish Bhobe
// =============================================
#include "aster/core/sampler.h"
#include "aster/core/device.h"
using namespace aster;
Sampler::~Sampler()
{
if (!IsValid())
return;
m_Device->m_Device.destroy(Take(m_Sampler), nullptr);
}
Sampler::Sampler(Device const *device, vk::SamplerCreateInfo const &samplerCreateInfo, cstr name)
{
m_Device = device;
auto const result = device->m_Device.createSampler(&samplerCreateInfo, nullptr, &m_Sampler);
ERROR_IF(Failed(result), "Could not create a sampler {}", name ? name : "<unnamed>") THEN_ABORT(-1);
}
Sampler &
Sampler::operator=(Sampler &&other) noexcept
{
if (this == &other)
return *this;
using std::swap;
swap(m_Device, other.m_Device);
swap(m_Sampler, other.m_Sampler);
return *this;
}
Sampler::Sampler(Sampler &&other) noexcept
: m_Device{other.m_Device}
, m_Sampler{Take(other.m_Sampler)}
{
}

View File

@ -1,20 +1,21 @@
// ============================================= // =============================================
// Aster: surface.cpp // Aster: surface.cpp
// Copyright (c) 2020-2024 Anish Bhobe // Copyright (c) 2020-2025 Anish Bhobe
// ============================================= // =============================================
#include "core/surface.h" #include "aster/core/surface.h"
#include "core/context.h" #include "aster/core/instance.h"
#include "core/window.h" #include "aster/core/window.h"
Surface::Surface(Context *context, const Window *window, cstr name) using namespace aster;
: m_Context(context)
, m_Name(name) Surface::Surface(Instance &context, Window const &window)
: m_Context(&context)
{ {
VkSurfaceKHR surface; VkSurfaceKHR surface;
auto result = Cast<vk::Result>( auto result = static_cast<vk::Result>(
glfwCreateWindowSurface(Cast<VkInstance>(m_Context->m_Instance), window->m_Window, nullptr, &surface)); glfwCreateWindowSurface(static_cast<VkInstance>(m_Context->m_Instance), window.m_Window, nullptr, &surface));
ERROR_IF(Failed(result), "Failed to create Surface with {}", result) ERROR_IF(Failed(result), "Failed to create Surface with {}", result)
THEN_ABORT(result) THEN_ABORT(result)
ELSE_DEBUG("Surface {} Created", m_Name); ELSE_DEBUG("Surface {} Created", m_Name);
@ -23,14 +24,14 @@ Surface::Surface(Context *context, const Window *window, cstr name)
Surface::~Surface() Surface::~Surface()
{ {
if (m_Context && m_Surface) if (!m_Context || !m_Context->m_Instance || !m_Surface)
{ return;
m_Context->m_Instance.destroy(m_Surface, nullptr);
DEBUG("Surface Destroyed");
m_Surface = nullptr; m_Context->m_Instance.destroy(m_Surface, nullptr);
m_Context = nullptr; DEBUG("Surface Destroyed");
}
m_Surface = nullptr;
m_Context = nullptr;
} }
Surface::Surface(Surface &&other) noexcept Surface::Surface(Surface &&other) noexcept

View File

@ -1,19 +1,20 @@
/// ============================================= /// =============================================
// Aster: swapchain.cpp // Aster: swapchain.cpp
// Copyright (c) 2020-2024 Anish Bhobe // Copyright (c) 2020-2025 Anish Bhobe
// ============================================== // ==============================================
#include "core/swapchain.h" #include "aster/core/swapchain.h"
#include "core/device.h" #include "aster/core/device.h"
#include "core/physical_device.h" #include "aster/core/physical_device.h"
#include "core/surface.h" #include "aster/core/surface.h"
using namespace aster;
[[nodiscard]] vk::Extent2D GetExtent(Size2D size, vk::SurfaceCapabilitiesKHR *surfaceCapabilities); [[nodiscard]] vk::Extent2D GetExtent(Size2D size, vk::SurfaceCapabilitiesKHR *surfaceCapabilities);
Swapchain::Swapchain(const Surface *surface, const Device *device, Size2D size, NameString &&name) Swapchain::Swapchain(Surface const &surface, Device const &device, Size2D size)
: m_Device(device) : m_Device(&device)
, m_Name(std::move(name))
, m_Format(vk::Format::eUndefined) , m_Format(vk::Format::eUndefined)
{ {
this->Create(surface, size); this->Create(surface, size);
@ -27,11 +28,11 @@ Swapchain::~Swapchain()
Swapchain::Swapchain(Swapchain &&other) noexcept Swapchain::Swapchain(Swapchain &&other) noexcept
: m_Device(other.m_Device) : m_Device(other.m_Device)
, m_Swapchain(Take(other.m_Swapchain)) , m_Swapchain(Take(other.m_Swapchain))
, m_Name(std::move(other.m_Name))
, m_Extent(other.m_Extent) , m_Extent(other.m_Extent)
, m_Format(other.m_Format) , m_Format(other.m_Format)
, m_Images(std::move(other.m_Images)) , m_Images(std::move(other.m_Images))
, m_ImageViews(std::move(other.m_ImageViews)) , m_ImageViews(std::move(other.m_ImageViews))
, m_ResizeCallbacks(std::move(other.m_ResizeCallbacks))
{ {
} }
@ -42,32 +43,32 @@ Swapchain::operator=(Swapchain &&other) noexcept
return *this; return *this;
m_Device = other.m_Device; m_Device = other.m_Device;
m_Swapchain = Take(other.m_Swapchain); m_Swapchain = Take(other.m_Swapchain);
m_Name = std::move(other.m_Name);
m_Extent = other.m_Extent; m_Extent = other.m_Extent;
m_Format = other.m_Format; m_Format = other.m_Format;
m_Images = std::move(other.m_Images); m_Images = std::move(other.m_Images);
m_ImageViews = std::move(other.m_ImageViews); m_ImageViews = std::move(other.m_ImageViews);
m_ResizeCallbacks = std::move(other.m_ResizeCallbacks);
return *this; return *this;
} }
void void
Swapchain::Create(const Surface *surface, Size2D size) Swapchain::Create(Surface const &surface, Size2D size)
{ {
auto surfaceCapabilities = GetSurfaceCapabilities(m_Device->m_PhysicalDevice, surface->m_Surface); auto surfaceCapabilities = GetSurfaceCapabilities(m_Device->m_PhysicalDevice, surface.m_Surface);
m_Extent = GetExtent(size, &surfaceCapabilities); m_Extent = GetExtent(size, &surfaceCapabilities);
while (m_Extent.width == 0 || m_Extent.height == 0) while (m_Extent.width == 0 || m_Extent.height == 0)
{ {
glfwWaitEvents(); glfwWaitEvents();
surfaceCapabilities = GetSurfaceCapabilities(m_Device->m_PhysicalDevice, surface->m_Surface); surfaceCapabilities = GetSurfaceCapabilities(m_Device->m_PhysicalDevice, surface.m_Surface);
m_Extent = GetExtent(size, &surfaceCapabilities); m_Extent = GetExtent(size, &surfaceCapabilities);
} }
auto surfaceFormats = GetSurfaceFormats(m_Device->m_PhysicalDevice, surface->m_Surface); auto surfaceFormats = GetSurfaceFormats(m_Device->m_PhysicalDevice, surface.m_Surface);
auto presentModes = GetSurfacePresentModes(m_Device->m_PhysicalDevice, surface->m_Surface); auto presentModes = GetSurfacePresentModes(m_Device->m_PhysicalDevice, surface.m_Surface);
m_Format = vk::Format::eUndefined; m_Format = vk::Format::eUndefined;
vk::ColorSpaceKHR swapchainColorSpace = vk::ColorSpaceKHR::eSrgbNonlinear; auto swapchainColorSpace = vk::ColorSpaceKHR::eSrgbNonlinear;
for (auto [format, colorSpace] : surfaceFormats) for (auto [format, colorSpace] : surfaceFormats)
{ {
if (format == vk::Format::eB8G8R8A8Srgb && colorSpace == vk::ColorSpaceKHR::eSrgbNonlinear) if (format == vk::Format::eB8G8R8A8Srgb && colorSpace == vk::ColorSpaceKHR::eSrgbNonlinear)
@ -84,8 +85,8 @@ Swapchain::Create(const Surface *surface, Size2D size)
swapchainColorSpace = colorSpace; swapchainColorSpace = colorSpace;
} }
vk::PresentModeKHR swapchainPresentMode = vk::PresentModeKHR::eFifo; auto swapchainPresentMode = vk::PresentModeKHR::eFifo;
for (const auto presentMode : presentModes) for (auto const presentMode : presentModes)
{ {
if (presentMode == vk::PresentModeKHR::eMailbox) if (presentMode == vk::PresentModeKHR::eMailbox)
{ {
@ -95,16 +96,14 @@ Swapchain::Create(const Surface *surface, Size2D size)
} }
u32 swapchainImageCount = 3; u32 swapchainImageCount = 3;
if (surfaceCapabilities.maxImageCount > 0) u32 maxImageCount =
{ glm::max(swapchainImageCount, glm::max(surfaceCapabilities.maxImageCount, surfaceCapabilities.minImageCount));
swapchainImageCount = swapchainImageCount = glm::clamp(swapchainImageCount, surfaceCapabilities.minImageCount, maxImageCount);
glm::clamp(swapchainImageCount, surfaceCapabilities.minImageCount, surfaceCapabilities.maxImageCount);
}
// TODO: Note that different queues might need the images to be shared. // TODO: Note that different queues might need the images to be shared.
const vk::SwapchainCreateInfoKHR swapchainCreateInfo = { vk::SwapchainCreateInfoKHR const swapchainCreateInfo = {
.surface = surface->m_Surface, .surface = surface.m_Surface,
.minImageCount = swapchainImageCount, .minImageCount = swapchainImageCount,
.imageFormat = m_Format, .imageFormat = m_Format,
.imageColorSpace = swapchainColorSpace, .imageColorSpace = swapchainColorSpace,
@ -120,28 +119,30 @@ Swapchain::Create(const Surface *surface, Size2D size)
}; };
vk::Device device = m_Device->m_Device; vk::Device device = m_Device->m_Device;
NameString name = "Swapchain of ";
name += m_Device->m_Name;
vk::SwapchainKHR swapchain; vk::SwapchainKHR swapchain;
vk::Result result = device.createSwapchainKHR(&swapchainCreateInfo, nullptr, &swapchain); vk::Result result = device.createSwapchainKHR(&swapchainCreateInfo, nullptr, &swapchain);
ERROR_IF(Failed(result), "Swapchain {} creation failed. Cause {}", m_Name, result) ERROR_IF(Failed(result), "'{}' creation failed. Cause {}", name, result)
THEN_ABORT(result) THEN_ABORT(result)
ELSE_DEBUG("Created Swapchain '{}'", m_Name); ELSE_DEBUG("Created '{}'", name);
// Irrelevant on the first run. Required for re-creation. // Irrelevant on the first run. Required for re-creation.
Cleanup(); Cleanup();
m_Swapchain = swapchain; m_Swapchain = swapchain;
m_Device->SetName(m_Swapchain, m_Name.data()); m_Device->SetName(m_Swapchain, m_Device->m_Name.data());
result = device.getSwapchainImagesKHR(m_Swapchain, &swapchainImageCount, nullptr); result = device.getSwapchainImagesKHR(m_Swapchain, &swapchainImageCount, nullptr);
ERROR_IF(Failed(result), "Failed getting swapchain {}'s images. Cause {}", m_Name, result) ERROR_IF(Failed(result), "Failed getting {}'s images. Cause {}", name, result)
THEN_ABORT(result); THEN_ABORT(result);
// Managed by the Swapchain. // Managed by the Swapchain.
m_Images.resize(swapchainImageCount); m_Images.resize(swapchainImageCount, nullptr);
result = device.getSwapchainImagesKHR(m_Swapchain, &swapchainImageCount, m_Images.data()); result = device.getSwapchainImagesKHR(m_Swapchain, &swapchainImageCount, m_Images.data());
ERROR_IF(Failed(result), "Failed getting swapchain {}'s images. Cause {}", m_Name, result) ERROR_IF(Failed(result), "Failed getting {}'s images. Cause {}", name, result)
THEN_ABORT(result); THEN_ABORT(result);
vk::ImageViewCreateInfo viewCreateInfo = { vk::ImageViewCreateInfo viewCreateInfo = {
@ -165,7 +166,7 @@ Swapchain::Create(const Surface *surface, Size2D size)
vk::ImageView imageView; vk::ImageView imageView;
result = device.createImageView(&viewCreateInfo, nullptr, &imageView); result = device.createImageView(&viewCreateInfo, nullptr, &imageView);
ERROR_IF(Failed(result), "Failed creating swapchain {}'s image view [{}]. Cause {}", m_Name, index, result) ERROR_IF(Failed(result), "Failed creating {}'s image view [{}]. Cause {}", name, index, result)
THEN_ABORT(result); THEN_ABORT(result);
m_ImageViews.push_back(imageView); m_ImageViews.push_back(imageView);
@ -173,7 +174,7 @@ Swapchain::Create(const Surface *surface, Size2D size)
++index; ++index;
} }
DEBUG("Swapchain {} Image Views created.", m_Name); DEBUG("{} Image Views created.", name);
for (auto &callback : m_ResizeCallbacks) for (auto &callback : m_ResizeCallbacks)
{ {
@ -184,24 +185,31 @@ Swapchain::Create(const Surface *surface, Size2D size)
void void
Swapchain::RegisterResizeCallback(FnResizeCallback &&callback) Swapchain::RegisterResizeCallback(FnResizeCallback &&callback)
{ {
m_ResizeCallbacks.emplace_back(callback); m_ResizeCallbacks.emplace_back(std::move(callback));
} }
void void
Swapchain::Cleanup() Swapchain::Cleanup()
{ {
if (!m_ImageViews.empty()) // Don't want the condition in the logs. if (!m_Swapchain)
DEBUG("Swapchain {} Image Views destroyed.", m_Name); return;
for (const auto imageView : m_ImageViews)
NameString name = "Swapchain of ";
name += m_Device->m_Name;
for (auto const imageView : m_ImageViews)
{ {
m_Device->m_Device.destroy(imageView, nullptr); m_Device->m_Device.destroy(imageView, nullptr);
} }
if (!m_ImageViews.empty()) // Don't want the condition in the logs.
DEBUG("Swapchain {} Image Views destroyed.", name);
m_ImageViews.clear(); m_ImageViews.clear();
m_Images.clear();
if (m_Swapchain) if (m_Swapchain)
{ {
m_Device->m_Device.destroy(m_Swapchain, nullptr); m_Device->m_Device.destroy(m_Swapchain, nullptr);
m_Swapchain = nullptr; m_Swapchain = nullptr;
DEBUG("Swapchain '{}' destroyed.", m_Name); DEBUG("Swapchain '{}' destroyed.", name);
} }
} }

View File

@ -1,16 +1,42 @@
// ============================================= // =============================================
// Aster: window.cpp // Aster: window.cpp
// Copyright (c) 2020-2024 Anish Bhobe // Copyright (c) 2020-2025 Anish Bhobe
// ============================================= // =============================================
#include "core/window.h" #include "aster/core/window.h"
#include "core/context.h" #include "aster/core/instance.h"
#include "util/logger.h" #include "aster/util/logger.h"
using namespace aster;
std::atomic_uint64_t Window::m_WindowCount = 0; std::atomic_uint64_t Window::m_WindowCount = 0;
std::atomic_bool Window::m_IsGlfwInit = false; std::atomic_bool Window::m_IsGlfwInit = false;
void
Window::SetupLibrary()
{
if (!m_IsGlfwInit)
{
if (!glfwInit())
{
char const *error = nullptr;
auto const code = glfwGetError(&error);
ERROR("GLFW Init failed. Cause: ({}) {}", code, error)
THEN_ABORT(code);
}
m_WindowCount = 0;
m_IsGlfwInit = true;
}
}
cstr *
Window::GetInstanceExtensions(u32 *extensionCount)
{
SetupLibrary();
return glfwGetRequiredInstanceExtensions(extensionCount);
}
void void
Window::RequestExit() const noexcept Window::RequestExit() const noexcept
{ {
@ -18,15 +44,15 @@ Window::RequestExit() const noexcept
} }
void void
Window::SetWindowSize(const vk::Extent2D &extent) const noexcept Window::SetWindowSize(vk::Extent2D const &extent) const noexcept
{ {
SetWindowSize(extent.width, extent.height); SetWindowSize(extent.width, extent.height);
} }
void void
Window::SetWindowSize(const u32 width, const u32 height) const noexcept Window::SetWindowSize(u32 const width, u32 const height) const noexcept
{ {
glfwSetWindowSize(m_Window, Cast<i32>(width), Cast<i32>(height)); glfwSetWindowSize(m_Window, static_cast<i32>(width), static_cast<i32>(height));
} }
Size2D Size2D
@ -35,25 +61,14 @@ Window::GetSize() const
int width; int width;
int height; int height;
glfwGetFramebufferSize(m_Window, &width, &height); glfwGetFramebufferSize(m_Window, &width, &height);
return {Cast<u32>(width), Cast<u32>(height)}; return {static_cast<u32>(width), static_cast<u32>(height)};
} }
Window::Window(const cstr title, Size2D extent, const b8 isFullScreen) Window::Window(cstr const title, Size2D extent, b8 const isFullScreen)
{ {
m_Name = title; m_Name = title;
if (!m_IsGlfwInit) SetupLibrary();
{
if (!glfwInit())
{
const char *error = nullptr;
const auto code = glfwGetError(&error);
ERROR("GLFW Init failed. Cause: ({}) {}", code, error)
THEN_ABORT(code);
}
m_WindowCount = 0;
m_IsGlfwInit = true;
}
GLFWmonitor *monitor = glfwGetPrimaryMonitor(); GLFWmonitor *monitor = glfwGetPrimaryMonitor();
ERROR_IF(!monitor, "No monitor found"); ERROR_IF(!monitor, "No monitor found");
@ -64,22 +79,22 @@ Window::Window(const cstr title, Size2D extent, const b8 isFullScreen)
glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API); glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API);
glfwWindowHint(GLFW_CENTER_CURSOR, GLFW_TRUE); glfwWindowHint(GLFW_CENTER_CURSOR, GLFW_TRUE);
m_Window = glfwCreateWindow(Cast<i32>(extent.m_Width), Cast<i32>(extent.m_Height), m_Name.c_str(), m_Window = glfwCreateWindow(static_cast<i32>(extent.m_Width), static_cast<i32>(extent.m_Height), m_Name.c_str(),
isFullScreen ? monitor : nullptr, nullptr); isFullScreen ? monitor : nullptr, nullptr);
ERROR_IF(m_Window == nullptr, "Window creation failed") ERROR_IF(m_Window == nullptr, "Window creation failed")
ELSE_DEBUG("Window '{}' created with resolution '{}x{}'", m_Name, extent.m_Width, extent.m_Height); ELSE_DEBUG("Window '{}' created with resolution '{}x{}'", m_Name, extent.m_Width, extent.m_Height);
if (m_Window == nullptr) if (m_Window == nullptr)
{ {
const char *error = nullptr; char const *error = nullptr;
const auto code = glfwGetError(&error); auto const code = glfwGetError(&error);
ERROR("GLFW Window Creation failed. Cause: ({}) {}", code, error) ERROR("GLFW Window Creation failed. Cause: ({}) {}", code, error)
THEN_ABORT(code); THEN_ABORT(code);
} }
if (isFullScreen == false) if (isFullScreen == false)
{ {
glfwSetWindowPos(m_Window, Cast<i32>(windowWidth - extent.m_Width) / 2, glfwSetWindowPos(m_Window, static_cast<i32>(windowWidth - extent.m_Width) / 2,
Cast<i32>(windowHeight - extent.m_Height) / 2); static_cast<i32>(windowHeight - extent.m_Height) / 2);
} }
glfwSetInputMode(m_Window, GLFW_CURSOR, GLFW_CURSOR_NORMAL); glfwSetInputMode(m_Window, GLFW_CURSOR, GLFW_CURSOR_NORMAL);
@ -96,7 +111,7 @@ Window::~Window()
--m_WindowCount; --m_WindowCount;
} }
if (m_WindowCount== 0 && m_IsGlfwInit) if (m_WindowCount == 0 && m_IsGlfwInit)
{ {
glfwTerminate(); glfwTerminate();
m_IsGlfwInit = false; m_IsGlfwInit = false;
@ -119,4 +134,4 @@ Window::operator=(Window &&other) noexcept
m_Window = Take(other.m_Window); m_Window = Take(other.m_Window);
m_Name = Take(other.m_Name); m_Name = Take(other.m_Name);
return *this; return *this;
} }

View File

@ -4,7 +4,8 @@ cmake_minimum_required(VERSION 3.13)
target_sources(aster_core target_sources(aster_core
PRIVATE PRIVATE
"manager.cpp" "rendering_device.cpp"
"buffer_manager.cpp" "commit_manager.cpp"
"image_manager.cpp" "pipeline_helpers.cpp"
"render_resource_manager.cpp") "context.cpp"
"sync_server.cpp")

View File

@ -1,48 +0,0 @@
// =============================================
// Aster: buffer_manager.cpp
// Copyright (c) 2020-2025 Anish Bhobe
// =============================================
#include "systems/buffer_manager.h"
Manager<Buffer> *Manager<Buffer>::m_Instance = nullptr;
using namespace systems;
BufferHandle
BufferManager::CreateStorageBuffer(const usize size, const cstr name)
{
auto [handle, object] = Alloc();
// TODO: Storage and Index buffer are set.
// This is hacky and should be improved.
constexpr vk::BufferUsageFlags usage = vk::BufferUsageFlagBits::eStorageBuffer | vk::BufferUsageFlagBits::eIndexBuffer |
vk::BufferUsageFlagBits::eShaderDeviceAddress;
constexpr VmaAllocationCreateFlags createFlags = VMA_ALLOCATION_CREATE_HOST_ACCESS_SEQUENTIAL_WRITE_BIT |
VMA_ALLOCATION_CREATE_HOST_ACCESS_ALLOW_TRANSFER_INSTEAD_BIT |
VMA_ALLOCATION_CREATE_MAPPED_BIT;
constexpr VmaMemoryUsage memoryUsage = VMA_MEMORY_USAGE_AUTO;
object->Allocate(m_Device, size, usage, createFlags, memoryUsage, name);
return std::move(handle);
}
Manager<Buffer>::Handle
BufferManager::CreateUniformBuffer(const usize size, const cstr name)
{
auto [handle, object] = Alloc();
constexpr vk::BufferUsageFlags usage = vk::BufferUsageFlagBits::eUniformBuffer;
constexpr VmaAllocationCreateFlags createFlags = VMA_ALLOCATION_CREATE_HOST_ACCESS_SEQUENTIAL_WRITE_BIT |
VMA_ALLOCATION_CREATE_HOST_ACCESS_ALLOW_TRANSFER_INSTEAD_BIT |
VMA_ALLOCATION_CREATE_MAPPED_BIT;
constexpr VmaMemoryUsage memoryUsage = VMA_MEMORY_USAGE_AUTO;
object->Allocate(m_Device, size, usage, createFlags, memoryUsage, name);
return std::move(handle);
}
BufferManager::BufferManager(const Device *device, const u32 maxCount, const u8 binding)
: Manager{device, maxCount, binding}
{
}

View File

@ -0,0 +1,239 @@
// =============================================
// Aster: render_resource_manager.cpp
// Copyright (c) 2020-2025 Anish Bhobe
// =============================================
#include "aster/systems/commit_manager.h"
#include "EASTL/array.h"
#include "aster/core/device.h"
#include "aster/core/image_view.h"
#include "aster/systems/rendering_device.h"
using namespace aster;
CommitManager *CommitManager::m_Instance = nullptr;
CommitManager::CommitManager(RenderingDevice const *device, u32 const maxBuffers, u32 const maxImages,
u32 const maxStorageImages, Ref<Sampler> defaultSampler)
: m_Device{device}
, m_Buffers{maxBuffers}
, m_Images{maxImages}
, m_StorageImages{maxStorageImages}
, m_DefaultSampler{std::move(defaultSampler)}
{
assert(!m_Instance);
eastl::array poolSizes = {
vk::DescriptorPoolSize{
.type = vk::DescriptorType::eStorageBuffer,
.descriptorCount = maxBuffers,
},
vk::DescriptorPoolSize{
.type = vk::DescriptorType::eCombinedImageSampler,
.descriptorCount = maxImages,
},
vk::DescriptorPoolSize{
.type = vk::DescriptorType::eStorageImage,
.descriptorCount = maxStorageImages,
},
};
vk::DescriptorPoolCreateInfo const poolCreateInfo = {
.flags = vk::DescriptorPoolCreateFlagBits::eUpdateAfterBind,
.maxSets = 1,
.poolSizeCount = static_cast<u32>(poolSizes.size()),
.pPoolSizes = poolSizes.data(),
};
AbortIfFailed(device->m_Device->createDescriptorPool(&poolCreateInfo, nullptr, &m_DescriptorPool));
eastl::array descriptorLayoutBindings = {
vk::DescriptorSetLayoutBinding{
.binding = BUFFER_BINDING_INDEX,
.descriptorType = vk::DescriptorType::eStorageBuffer,
.descriptorCount = static_cast<u32>(maxBuffers),
.stageFlags = vk::ShaderStageFlagBits::eAll,
},
vk::DescriptorSetLayoutBinding{
.binding = IMAGE_BINDING_INDEX,
.descriptorType = vk::DescriptorType::eCombinedImageSampler,
.descriptorCount = static_cast<u32>(maxImages),
.stageFlags = vk::ShaderStageFlagBits::eAll,
},
vk::DescriptorSetLayoutBinding{
.binding = STORAGE_IMAGE_BINDING_INDEX,
.descriptorType = vk::DescriptorType::eStorageImage,
.descriptorCount = static_cast<u32>(maxStorageImages),
.stageFlags = vk::ShaderStageFlagBits::eAll,
},
};
vk::DescriptorBindingFlags bindingFlags =
vk::DescriptorBindingFlagBits::ePartiallyBound | vk::DescriptorBindingFlagBits::eUpdateAfterBind;
eastl::array<vk::DescriptorBindingFlags, descriptorLayoutBindings.size()> layoutBindingFlags;
layoutBindingFlags.fill(bindingFlags);
vk::DescriptorSetLayoutBindingFlagsCreateInfo bindingFlagsCreateInfo = {
.bindingCount = static_cast<u32>(layoutBindingFlags.size()),
.pBindingFlags = layoutBindingFlags.data(),
};
static_assert(layoutBindingFlags.size() == descriptorLayoutBindings.size());
vk::DescriptorSetLayoutCreateInfo const descriptorSetLayoutCreateInfo = {
.pNext = &bindingFlagsCreateInfo,
.flags = vk::DescriptorSetLayoutCreateFlagBits::eUpdateAfterBindPool,
.bindingCount = static_cast<u32>(descriptorLayoutBindings.size()),
.pBindings = descriptorLayoutBindings.data(),
};
AbortIfFailed(device->m_Device->createDescriptorSetLayout(&descriptorSetLayoutCreateInfo, nullptr, &m_SetLayout));
// One descriptor is enough. Updating it at any time is safe. (Update until submit, data held when pending)
// https://registry.khronos.org/vulkan/specs/1.3-extensions/man/html/VK_EXT_descriptor_indexing.html
// https://github.com/KhronosGroup/Vulkan-Guide/blob/main/chapters/extensions/VK_EXT_descriptor_indexing.adoc
vk::DescriptorSetAllocateInfo const descriptorSetAllocateInfo = {
.descriptorPool = m_DescriptorPool,
.descriptorSetCount = 1,
.pSetLayouts = &m_SetLayout,
};
AbortIfFailed(device->m_Device->allocateDescriptorSets(&descriptorSetAllocateInfo, &m_DescriptorSet));
device->SetName(m_SetLayout, "Bindless Layout");
device->SetName(m_DescriptorPool, "Bindless Pool");
device->SetName(m_DescriptorSet, "Bindless Set");
m_Instance = this;
}
CommitManager::~CommitManager()
{
m_Device->m_Device->destroy(m_SetLayout, nullptr);
m_Device->m_Device->destroy(m_DescriptorPool, nullptr);
#if !defined(ASTER_NDEBUG)
u32 bufferCount = 0;
for (auto const &entry : m_Buffers.m_Data)
{
bufferCount += entry.m_CommitCount;
}
u32 imageCount = 0;
for (auto const &entry : m_Images.m_Data)
{
imageCount += entry.m_CommitCount;
}
if (bufferCount > 0 || imageCount > 0)
{
WARN("Committed resources at destruction. Buffers: {}, Images: {}", bufferCount, imageCount);
}
#endif
}
ResId<Buffer>
CommitManager::CommitBuffer(Ref<Buffer> const &buffer)
{
auto [commit, isNew] = m_Buffers.Create(buffer);
if (!isNew)
return commit;
m_WriteInfos.emplace_back(vk::DescriptorBufferInfo{
.buffer = buffer->m_Buffer,
.offset = 0,
.range = buffer->m_Size,
});
m_Writes.push_back({
.dstSet = m_DescriptorSet,
.dstBinding = BUFFER_BINDING_INDEX,
.dstArrayElement = commit.m_Index,
.descriptorCount = 1,
.descriptorType = vk::DescriptorType::eStorageBuffer,
.pBufferInfo = &m_WriteInfos.back().uBufferInfo,
});
return commit;
}
ResId<StorageImageView>
CommitManager::CommitStorageImage(Ref<StorageImageView> const &image)
{
auto [commit, isNew] = m_StorageImages.Create(image);
if (!isNew)
return commit;
m_WriteInfos.emplace_back(vk::DescriptorImageInfo{
.sampler = nullptr,
.imageView = image->m_View,
.imageLayout = vk::ImageLayout::eGeneral,
});
m_Writes.push_back({
.dstSet = m_DescriptorSet,
.dstBinding = STORAGE_IMAGE_BINDING_INDEX,
.dstArrayElement = commit.m_Index,
.descriptorCount = 1,
.descriptorType = vk::DescriptorType::eStorageImage,
.pImageInfo = &m_WriteInfos.back().uImageInfo,
});
return commit;
}
ResId<TextureView>
CommitManager::CommitTexture(Ref<TextureView> const &handle)
{
return CommitTexture(handle, m_DefaultSampler);
}
ResId<TextureView>
CommitManager::CommitTexture(Ref<TextureView> const &image, Ref<Sampler> const &sampler)
{
auto [commit, isNew] = m_Images.Create(image);
if (!isNew)
return commit;
m_WriteInfos.emplace_back(vk::DescriptorImageInfo{
.sampler = sampler->m_Sampler,
.imageView = image->m_View,
.imageLayout = vk::ImageLayout::eShaderReadOnlyOptimal,
});
m_Writes.push_back({
.dstSet = m_DescriptorSet,
.dstBinding = IMAGE_BINDING_INDEX,
.dstArrayElement = commit.m_Index,
.descriptorCount = 1,
.descriptorType = vk::DescriptorType::eCombinedImageSampler,
.pImageInfo = &m_WriteInfos.back().uImageInfo,
});
return commit;
}
CommitManager::WriteInfo::WriteInfo(vk::DescriptorBufferInfo const &info)
: uBufferInfo{info}
{
}
CommitManager::WriteInfo::WriteInfo(vk::DescriptorImageInfo const &info)
: uImageInfo{info}
{
}
CommitManager::WriteInfo::WriteInfo(vk::BufferView const &info)
: uBufferView{info}
{
}
void
CommitManager::Update()
{
// Descriptor Updates
if (!m_Writes.empty())
{
m_Device->m_Device->updateDescriptorSets(static_cast<u32>(m_Writes.size()), m_Writes.data(), 0, nullptr);
m_Writes.clear();
m_WriteInfos.clear();
}
m_Buffers.Update();
m_Images.Update();
}

View File

@ -0,0 +1,508 @@
// =============================================
// Aster: context.cpp
// Copyright (c) 2020-2025 Anish Bhobe
// =============================================
#include "aster/systems/context.h"
#include "aster/systems/commit_manager.h"
#include "aster/systems/rendering_device.h"
using namespace aster;
constexpr static u32
GetFormatSize(vk::Format const format)
{
switch (format)
{
case vk::Format::eUndefined:
return 0;
case vk::Format::eR8Unorm:
case vk::Format::eR8Snorm:
case vk::Format::eR8Uscaled:
case vk::Format::eR8Sscaled:
case vk::Format::eR8Uint:
case vk::Format::eR8Sint:
case vk::Format::eR8Srgb:
return 1;
case vk::Format::eR8G8Unorm:
case vk::Format::eR8G8Snorm:
case vk::Format::eR8G8Uscaled:
case vk::Format::eR8G8Sscaled:
case vk::Format::eR8G8Uint:
case vk::Format::eR8G8Sint:
case vk::Format::eR8G8Srgb:
return 2;
case vk::Format::eR8G8B8Unorm:
case vk::Format::eR8G8B8Snorm:
case vk::Format::eR8G8B8Uscaled:
case vk::Format::eR8G8B8Sscaled:
case vk::Format::eR8G8B8Uint:
case vk::Format::eR8G8B8Sint:
case vk::Format::eR8G8B8Srgb:
case vk::Format::eB8G8R8Unorm:
case vk::Format::eB8G8R8Snorm:
case vk::Format::eB8G8R8Uscaled:
case vk::Format::eB8G8R8Sscaled:
case vk::Format::eB8G8R8Uint:
case vk::Format::eB8G8R8Sint:
case vk::Format::eB8G8R8Srgb:
return 3;
case vk::Format::eR8G8B8A8Unorm:
case vk::Format::eR8G8B8A8Snorm:
case vk::Format::eR8G8B8A8Uscaled:
case vk::Format::eR8G8B8A8Sscaled:
case vk::Format::eR8G8B8A8Uint:
case vk::Format::eR8G8B8A8Sint:
case vk::Format::eR8G8B8A8Srgb:
case vk::Format::eB8G8R8A8Unorm:
case vk::Format::eB8G8R8A8Snorm:
case vk::Format::eB8G8R8A8Uscaled:
case vk::Format::eB8G8R8A8Sscaled:
case vk::Format::eB8G8R8A8Uint:
case vk::Format::eB8G8R8A8Sint:
case vk::Format::eB8G8R8A8Srgb:
return 4;
case vk::Format::eR16Unorm:
case vk::Format::eR16Snorm:
case vk::Format::eR16Uscaled:
case vk::Format::eR16Sscaled:
case vk::Format::eR16Uint:
case vk::Format::eR16Sint:
case vk::Format::eR16Sfloat:
return 2;
case vk::Format::eR16G16Unorm:
case vk::Format::eR16G16Snorm:
case vk::Format::eR16G16Uscaled:
case vk::Format::eR16G16Sscaled:
case vk::Format::eR16G16Uint:
case vk::Format::eR16G16Sint:
case vk::Format::eR16G16Sfloat:
return 4;
case vk::Format::eR16G16B16Unorm:
case vk::Format::eR16G16B16Snorm:
case vk::Format::eR16G16B16Uscaled:
case vk::Format::eR16G16B16Sscaled:
case vk::Format::eR16G16B16Uint:
case vk::Format::eR16G16B16Sint:
case vk::Format::eR16G16B16Sfloat:
return 6;
case vk::Format::eR16G16B16A16Unorm:
case vk::Format::eR16G16B16A16Snorm:
case vk::Format::eR16G16B16A16Uscaled:
case vk::Format::eR16G16B16A16Sscaled:
case vk::Format::eR16G16B16A16Uint:
case vk::Format::eR16G16B16A16Sint:
case vk::Format::eR16G16B16A16Sfloat:
return 8;
case vk::Format::eR32Uint:
case vk::Format::eR32Sint:
case vk::Format::eR32Sfloat:
return 4;
case vk::Format::eR32G32Uint:
case vk::Format::eR32G32Sint:
case vk::Format::eR32G32Sfloat:
return 8;
case vk::Format::eR32G32B32Uint:
case vk::Format::eR32G32B32Sint:
case vk::Format::eR32G32B32Sfloat:
return 12;
case vk::Format::eR32G32B32A32Uint:
case vk::Format::eR32G32B32A32Sint:
case vk::Format::eR32G32B32A32Sfloat:
return 16;
case vk::Format::eD16Unorm:
return 2;
case vk::Format::eD32Sfloat:
return 4;
case vk::Format::eS8Uint:
return 1;
case vk::Format::eD16UnormS8Uint:
return 6;
case vk::Format::eD24UnormS8Uint:
return 4;
case vk::Format::eD32SfloatS8Uint:
return 5;
default:
TODO("Esoteric Formats");
}
return 0;
}
void
Context::KeepAlive(Ref<Buffer> const &buffer)
{
assert(m_Pool);
m_Pool->KeepAlive(buffer);
}
void
Context::KeepAlive(Ref<Image> const &image)
{
assert(m_Pool);
m_Pool->KeepAlive(image);
}
void
Context::KeepAlive(Ref<ImageView> const &view)
{
assert(m_Pool);
m_Pool->KeepAlive(view);
}
void
Context::Dependency(vk::DependencyInfo const &dependencyInfo)
{
m_Cmd.pipelineBarrier2(&dependencyInfo);
}
void
Context::Begin()
{
vk::CommandBufferBeginInfo commandBufferBeginInfo = {
.flags = vk::CommandBufferUsageFlagBits::eOneTimeSubmit,
};
auto result = m_Cmd.begin(&commandBufferBeginInfo);
ERROR_IF(Failed(result), "Could not begin context") THEN_ABORT(result);
}
// Release versions inline 'no-op'.
#if !defined(ASTER_NDEBUG)
void
Context::BeginDebugRegion(cstr const name, vec4 const color)
{
vk::DebugUtilsLabelEXT const label = {
.pLabelName = name,
.color = std::array{color.r, color.g, color.b, color.a},
};
m_Cmd.beginDebugUtilsLabelEXT(&label);
}
void
Context::EndDebugRegion()
{
m_Cmd.endDebugUtilsLabelEXT();
}
#endif
void
Context::End()
{
auto result = m_Cmd.end();
ERROR_IF(Failed(result), "Could not end context") THEN_ABORT(result);
}
void
ComputeContext::Dispatch(Pipeline const &pipeline, u32 x, u32 y, u32 z, usize size, void *data)
{
BindPipeline(pipeline);
PushConstantBlock(0, size, data);
m_Cmd.dispatch(x, y, z);
}
void
ComputeContext::BindPipeline(Pipeline const &pipeline)
{
auto bindPoint = vk::PipelineBindPoint::eGraphics;
switch (pipeline.m_Kind)
{
case Pipeline::Kind::eGraphics:
bindPoint = vk::PipelineBindPoint::eGraphics;
break;
case Pipeline::Kind::eCompute:
bindPoint = vk::PipelineBindPoint::eCompute;
break;
default:
UNREACHABLE("No additional bind points");
}
m_Cmd.bindPipeline(bindPoint, pipeline.m_Pipeline);
// TODO: Maybe find a smarter place to host this.
if (CommitManager::IsInit())
{
m_Cmd.bindDescriptorSets(bindPoint, pipeline.m_Layout, 0, 1, &CommitManager::Instance().GetDescriptorSet(), 0,
nullptr);
}
m_PipelineInUse = &pipeline;
}
void
GraphicsContext::SetViewport(vk::Viewport const &viewport)
{
m_Cmd.setViewport(0, 1, &viewport);
}
void
GraphicsContext::BindVertexBuffer(Ref<VertexBuffer> const &vertexBuffer)
{
constexpr vk::DeviceSize offset = 0;
m_Cmd.bindVertexBuffers(0, 1, &vertexBuffer->m_Buffer, &offset);
}
void
GraphicsContext::BindIndexBuffer(Ref<IndexBuffer> const &indexBuffer)
{
m_Cmd.bindIndexBuffer(indexBuffer->m_Buffer, 0, vk::IndexType::eUint32);
}
void
GraphicsContext::Draw(usize const vertexCount)
{
m_Cmd.draw(static_cast<u32>(vertexCount), 1, 0, 0);
}
void
GraphicsContext::DrawIndexed(usize indexCount)
{
m_Cmd.drawIndexed(static_cast<u32>(indexCount), 1, 0, 0, 0);
}
void
GraphicsContext::DrawIndexed(usize const indexCount, usize const firstIndex, usize const firstVertex)
{
m_Cmd.drawIndexed(static_cast<u32>(indexCount), 1, static_cast<u32>(firstIndex), static_cast<i32>(firstVertex), 0);
}
void
GraphicsContext::BeginRendering(vk::RenderingInfo const &renderingInfo)
{
m_Cmd.beginRendering(&renderingInfo);
m_Cmd.setScissor(0, 1, &renderingInfo.renderArea);
}
void
GraphicsContext::EndRendering()
{
m_Cmd.endRendering();
}
void
TransferContext::UploadTexture(Ref<Image> const &image, eastl::span<u8> const &data)
{
ERROR_IF(not(image and image->IsValid()), "Invalid image");
auto [w, h, d] = image->m_Extent;
auto formatSize = GetFormatSize(image->m_Format);
auto expectedByteSize = static_cast<u64>(w) * static_cast<u64>(h) * static_cast<u64>(d) * formatSize;
ERROR_IF(expectedByteSize != data.size_bytes(), "Mismatch in data size {} vs image size {} ({}x{}x{}x{})",
data.size_bytes(), expectedByteSize, w, h, d, formatSize);
Ref<StagingBuffer> const stagingBuffer = m_Pool->GetDevice().CreateStagingBuffer(data.size_bytes());
stagingBuffer->Write(0, data.size_bytes(), data.data());
vk::BufferImageCopy const bufferImageCopy = {
.bufferOffset = 0,
.bufferRowLength = w,
.bufferImageHeight = h,
.imageSubresource =
{
.aspectMask = vk::ImageAspectFlagBits::eColor,
.mipLevel = 0,
.baseArrayLayer = 0,
.layerCount = 1,
},
.imageOffset = {},
.imageExtent = image->m_Extent,
};
m_Cmd.copyBufferToImage(stagingBuffer->m_Buffer, image->m_Image, vk::ImageLayout::eTransferDstOptimal, 1,
&bufferImageCopy);
KeepAlive(stagingBuffer);
KeepAlive(image);
}
void
TransferContext::UploadBuffer(Ref<Buffer> const &buffer, usize size, void const *data)
{
ERROR_IF(not(buffer and buffer->IsValid()), "Invalid buffer");
auto expectedByteSize = buffer->m_Size;
ERROR_IF(expectedByteSize != size, "Mismatch in data size {} vs buffer size {}", size, expectedByteSize);
Ref<StagingBuffer> const stagingBuffer = m_Pool->GetDevice().CreateStagingBuffer(size);
stagingBuffer->Write(0, size, data);
vk::BufferCopy const bufferCopy = {.srcOffset = 0, .dstOffset = 0, .size = expectedByteSize};
m_Cmd.copyBuffer(stagingBuffer->m_Buffer, buffer->m_Buffer, 1, &bufferCopy);
KeepAlive(stagingBuffer);
KeepAlive(buffer);
}
void
TransferContext::Blit(vk::BlitImageInfo2 const &mipBlitInfo)
{
m_Cmd.blitImage2(&mipBlitInfo);
}
TransferContext::TransferContext(TransferContext &&other) noexcept
: Context{std::move(other)}
{
}
TransferContext &
TransferContext::operator=(TransferContext &&other) noexcept
{
if (this == &other)
return *this;
Context::operator=(std::move(other));
return *this;
}
void
ComputeContext::PushConstantBlock(usize const offset, usize const size, void const *data)
{
assert(m_PipelineInUse);
vk::ShaderStageFlags stage;
switch (m_PipelineInUse->m_Kind)
{
case Pipeline::Kind::eGraphics:
stage = vk::ShaderStageFlagBits::eAll;
break;
case Pipeline::Kind::eCompute:
stage = vk::ShaderStageFlagBits::eCompute;
break;
}
m_Cmd.pushConstants(m_PipelineInUse->m_Layout, stage, static_cast<u32>(offset), static_cast<u32>(size), data);
}
using namespace _internal;
ContextPool::ContextPool(RenderingDevice &device, u32 const queueFamilyIndex, ManagedBy const managedBy)
: m_Device{&device}
, m_BuffersAllocated{0}
, m_ExtraData{0}
, m_ManagedBy{managedBy}
, m_ResetCallback{}
{
vk::CommandPoolCreateInfo const commandPoolCreateInfo = {
.flags = vk::CommandPoolCreateFlagBits::eTransient,
.queueFamilyIndex = queueFamilyIndex,
};
AbortIfFailed(device.m_Device->createCommandPool(&commandPoolCreateInfo, nullptr, &m_Pool));
}
ContextPool::ContextPool(ContextPool &&other) noexcept
: m_Device{other.m_Device}
, m_Pool{Take(other.m_Pool)}
, m_CommandBuffers{std::move(other.m_CommandBuffers)}
, m_BuffersAllocated{other.m_BuffersAllocated}
, m_ExtraData{other.m_ExtraData}
, m_ManagedBy{other.m_ManagedBy}
, m_OwnedBuffers{std::move(other.m_OwnedBuffers)}
, m_OwnedImages{std::move(other.m_OwnedImages)}
, m_OwnedImageViews{std::move(other.m_OwnedImageViews)}
, m_ResetCallback{std::move(other.m_ResetCallback)}
{
}
ContextPool &
ContextPool::operator=(ContextPool &&other) noexcept
{
if (this == &other)
return *this;
using eastl::swap;
swap(m_Device, other.m_Device);
swap(m_Pool, other.m_Pool);
swap(m_CommandBuffers, other.m_CommandBuffers);
swap(m_ExtraData, other.m_ExtraData);
swap(m_ManagedBy, other.m_ManagedBy);
swap(m_BuffersAllocated, other.m_BuffersAllocated);
swap(m_OwnedBuffers, other.m_OwnedBuffers);
swap(m_OwnedImages, other.m_OwnedImages);
swap(m_OwnedImageViews, other.m_OwnedImageViews);
swap(m_ResetCallback, other.m_ResetCallback);
return *this;
}
ContextPool::~ContextPool()
{
if (!m_Pool)
return;
m_Device->m_Device->destroy(Take(m_Pool), nullptr);
}
void
ContextPool::KeepAlive(Ref<Buffer> const &buffer)
{
m_OwnedBuffers.push_back(buffer);
}
void
ContextPool::KeepAlive(Ref<Image> const &image)
{
m_OwnedImages.push_back(image);
}
void
ContextPool::KeepAlive(Ref<ImageView> const &view)
{
m_OwnedImageViews.push_back(view);
}
vk::CommandBuffer
ContextPool::AllocateCommandBuffer()
{
// Buffers are available.
if (m_BuffersAllocated < m_CommandBuffers.size())
{
return m_CommandBuffers[m_BuffersAllocated++];
}
// Allocate New Buffer.
vk::CommandBufferAllocateInfo const allocateInfo = {
.commandPool = m_Pool,
.level = vk::CommandBufferLevel::ePrimary,
.commandBufferCount = 1,
};
vk::CommandBuffer &cmd = m_CommandBuffers.emplace_back();
AbortIfFailed(m_Device->m_Device->allocateCommandBuffers(&allocateInfo, &cmd));
++m_BuffersAllocated;
return cmd;
}
Context
ContextPool::CreateContext()
{
return Context{*this, AllocateCommandBuffer()};
}
void
ContextPool::Reset()
{
assert(m_Pool);
AbortIfFailed(m_Device->m_Device->resetCommandPool(m_Pool, {}));
m_BuffersAllocated = 0;
m_OwnedBuffers.clear();
m_OwnedImages.clear();
m_OwnedImageViews.clear();
}
TransferContext
TransferContextPool::CreateTransferContext()
{
return TransferContext{*this, AllocateCommandBuffer()};
}
ComputeContext
ComputeContextPool::CreateComputeContext()
{
return ComputeContext{*this, AllocateCommandBuffer()};
}
GraphicsContext
GraphicsContextPool::CreateGraphicsContext()
{
return GraphicsContext{*this, AllocateCommandBuffer()};
}

View File

@ -1,316 +0,0 @@
// =============================================
// Aster: buffer_manager.cpp
// Copyright (c) 2020-2025 Anish Bhobe
// =============================================
#include "systems/image_manager.h"
#include "core/device.h"
Manager<Image> *Manager<Image>::m_Instance = nullptr;
using namespace systems;
vk::ImageCreateInfo ToImageCreateInfo(const Texture2DCreateInfo &createInfo);
vk::ImageCreateInfo ToImageCreateInfo(const TextureCubeCreateInfo &createInfo);
vk::ImageCreateInfo ToImageCreateInfo(const AttachmentCreateInfo &createInfo);
vk::ImageCreateInfo ToImageCreateInfo(const DepthStencilImageCreateInfo &createInfo);
namespace usage_flags
{
constexpr vk::ImageUsageFlags MIPMAP = vk::ImageUsageFlagBits::eTransferSrc | vk::ImageUsageFlagBits::eTransferDst;
constexpr vk::ImageUsageFlags SAMPLE = vk::ImageUsageFlagBits::eSampled | vk::ImageUsageFlagBits::eTransferDst;
constexpr vk::ImageUsageFlags STORAGE =
vk::ImageUsageFlagBits::eStorage | vk::ImageUsageFlagBits::eTransferDst | vk::ImageUsageFlagBits::eTransferSrc;
constexpr vk::ImageUsageFlags COLOR_ATTACHMENT =
vk::ImageUsageFlagBits::eColorAttachment | vk::ImageUsageFlagBits::eTransferSrc;
constexpr vk::ImageUsageFlags DEPTH_STENCIL_ATTACHMENT = vk::ImageUsageFlagBits::eDepthStencilAttachment;
} // namespace usage_flags
ImageHandle
ImageManager::CreateTexture2D(const Texture2DCreateInfo &createInfo)
{
constexpr VmaAllocationCreateInfo allocationCreateInfo = {
.flags = {},
.usage = VMA_MEMORY_USAGE_AUTO,
};
VkImage image;
VmaAllocation allocation;
vk::ImageCreateInfo imageCreateInfo = ToImageCreateInfo(createInfo);
auto result = Cast<vk::Result>(vmaCreateImage(m_Device->m_Allocator, Recast<VkImageCreateInfo *>(&imageCreateInfo),
&allocationCreateInfo, &image, &allocation, nullptr));
ERROR_IF(Failed(result), "Could not allocate image {}. Cause: {}", createInfo.m_Name, result) THEN_ABORT(result);
vk::ImageView view;
const vk::ImageViewCreateInfo imageViewCreateInfo = {
.image = image,
.viewType = vk::ImageViewType::e2D,
.format = imageCreateInfo.format,
.components = {},
.subresourceRange =
{
.aspectMask = vk::ImageAspectFlagBits::eColor,
.baseMipLevel = 0,
.levelCount = imageCreateInfo.mipLevels,
.baseArrayLayer = 0,
.layerCount = imageCreateInfo.arrayLayers,
},
};
result = m_Device->m_Device.createImageView(&imageViewCreateInfo, nullptr, &view);
ERROR_IF(Failed(result), "Could not create image view {}. Cause: {}", createInfo.m_Name, result)
THEN_ABORT(result);
auto [handle, object] = Alloc();
object->m_Image = image;
object->m_View = view;
object->m_Allocation = allocation;
object->m_Extent = imageCreateInfo.extent;
object->m_Flags_ = Image::OWNED_BIT | Image::VALID_BIT;
object->m_LayerCount = Cast<u8>(imageCreateInfo.arrayLayers);
object->m_MipLevels = Cast<u8>(imageCreateInfo.mipLevels);
m_Device->SetName(object->m_Image, createInfo.m_Name);
return handle;
}
ImageHandle
ImageManager::CreateTextureCube(const TextureCubeCreateInfo &createInfo)
{
constexpr VmaAllocationCreateInfo allocationCreateInfo = {
.flags = {},
.usage = VMA_MEMORY_USAGE_AUTO,
};
VkImage image;
VmaAllocation allocation;
vk::ImageCreateInfo imageCreateInfo = ToImageCreateInfo(createInfo);
auto result = Cast<vk::Result>(vmaCreateImage(m_Device->m_Allocator, Recast<VkImageCreateInfo *>(&imageCreateInfo),
&allocationCreateInfo, &image, &allocation, nullptr));
ERROR_IF(Failed(result), "Could not allocate image {}. Cause: {}", createInfo.m_Name, result) THEN_ABORT(result);
vk::ImageView view;
const vk::ImageViewCreateInfo imageViewCreateInfo = {
.image = image,
.viewType = vk::ImageViewType::eCube,
.format = imageCreateInfo.format,
.components = {},
.subresourceRange =
{
.aspectMask = vk::ImageAspectFlagBits::eColor,
.baseMipLevel = 0,
.levelCount = imageCreateInfo.mipLevels,
.baseArrayLayer = 0,
.layerCount = imageCreateInfo.arrayLayers,
},
};
result = m_Device->m_Device.createImageView(&imageViewCreateInfo, nullptr, &view);
ERROR_IF(Failed(result), "Could not create image view {}. Cause: {}", createInfo.m_Name, result)
THEN_ABORT(result);
auto [handle, object] = Alloc();
object->m_Image = image;
object->m_View = view;
object->m_Allocation = allocation;
object->m_Extent = imageCreateInfo.extent;
object->m_Flags_ = Image::OWNED_BIT | Image::VALID_BIT;
object->m_LayerCount = Cast<u8>(imageCreateInfo.arrayLayers);
object->m_MipLevels = Cast<u8>(imageCreateInfo.mipLevels);
m_Device->SetName(object->m_Image, createInfo.m_Name);
return handle;
}
ImageHandle
ImageManager::CreateAttachment(const AttachmentCreateInfo &createInfo)
{
constexpr VmaAllocationCreateInfo allocationCreateInfo = {
.flags = VMA_ALLOCATION_CREATE_DEDICATED_MEMORY_BIT,
.usage = VMA_MEMORY_USAGE_AUTO,
};
VkImage image;
VmaAllocation allocation;
vk::ImageCreateInfo imageCreateInfo = ToImageCreateInfo(createInfo);
auto result = Cast<vk::Result>(vmaCreateImage(m_Device->m_Allocator, Recast<VkImageCreateInfo *>(&imageCreateInfo),
&allocationCreateInfo, &image, &allocation, nullptr));
ERROR_IF(Failed(result), "Could not allocate image {}. Cause: {}", createInfo.m_Name, result) THEN_ABORT(result);
vk::ImageView view;
const vk::ImageViewCreateInfo imageViewCreateInfo = {
.image = image,
.viewType = vk::ImageViewType::e2D,
.format = imageCreateInfo.format,
.components = {},
.subresourceRange =
{
.aspectMask = vk::ImageAspectFlagBits::eColor,
.baseMipLevel = 0,
.levelCount = imageCreateInfo.mipLevels,
.baseArrayLayer = 0,
.layerCount = imageCreateInfo.arrayLayers,
},
};
result = m_Device->m_Device.createImageView(&imageViewCreateInfo, nullptr, &view);
ERROR_IF(Failed(result), "Could not create image view {}. Cause: {}", createInfo.m_Name, result)
THEN_ABORT(result);
auto [handle, object] = Alloc();
object->m_Image = image;
object->m_View = view;
object->m_Allocation = allocation;
object->m_Extent = imageCreateInfo.extent;
object->m_Flags_ = Image::OWNED_BIT | Image::VALID_BIT;
object->m_LayerCount = Cast<u8>(imageCreateInfo.arrayLayers);
object->m_MipLevels = Cast<u8>(imageCreateInfo.mipLevels);
m_Device->SetName(object->m_Image, createInfo.m_Name);
return handle;
}
ImageHandle
ImageManager::CreateDepthStencilImage(const DepthStencilImageCreateInfo &createInfo)
{
constexpr VmaAllocationCreateInfo allocationCreateInfo = {
.flags = VMA_ALLOCATION_CREATE_DEDICATED_MEMORY_BIT,
.usage = VMA_MEMORY_USAGE_AUTO,
};
VkImage image;
VmaAllocation allocation;
vk::ImageCreateInfo imageCreateInfo = ToImageCreateInfo(createInfo);
auto result = Cast<vk::Result>(vmaCreateImage(m_Device->m_Allocator, Recast<VkImageCreateInfo *>(&imageCreateInfo),
&allocationCreateInfo, &image, &allocation, nullptr));
ERROR_IF(Failed(result), "Could not allocate image {}. Cause: {}", createInfo.m_Name, result) THEN_ABORT(result);
vk::ImageView view;
const vk::ImageViewCreateInfo imageViewCreateInfo = {
.image = image,
.viewType = vk::ImageViewType::e2D,
.format = imageCreateInfo.format,
.components = {},
.subresourceRange =
{
.aspectMask = vk::ImageAspectFlagBits::eDepth | vk::ImageAspectFlagBits::eStencil,
.baseMipLevel = 0,
.levelCount = imageCreateInfo.mipLevels,
.baseArrayLayer = 0,
.layerCount = imageCreateInfo.arrayLayers,
},
};
result = m_Device->m_Device.createImageView(&imageViewCreateInfo, nullptr, &view);
ERROR_IF(Failed(result), "Could not create image view {}. Cause: {}", createInfo.m_Name, result)
THEN_ABORT(result);
auto [handle, object] = Alloc();
object->m_Image = image;
object->m_View = view;
object->m_Allocation = allocation;
object->m_Extent = imageCreateInfo.extent;
object->m_Flags_ = Image::OWNED_BIT | Image::VALID_BIT;
object->m_LayerCount = Cast<u8>(imageCreateInfo.arrayLayers);
object->m_MipLevels = Cast<u8>(imageCreateInfo.mipLevels);
m_Device->SetName(object->m_Image, createInfo.m_Name);
return handle;
}
vk::ImageCreateInfo
ToImageCreateInfo(const Texture2DCreateInfo &createInfo)
{
auto &[format, extent, name, isSampled, isMipMapped, isStorage] = createInfo;
WARN_IF(!IsPowerOfTwo(extent.width) || !IsPowerOfTwo(extent.width), "Image {2} is {0}x{1} (Non Power of Two)",
extent.width, extent.height, name ? name : "<unnamed>");
const u8 mipLevels = isMipMapped ? 1 + Cast<u8>(floor(log2(eastl::max(extent.width, extent.height)))) : 1;
auto usage = vk::ImageUsageFlags{};
if (isSampled)
usage |= usage_flags::SAMPLE;
if (isMipMapped)
usage |= usage_flags::MIPMAP;
if (isStorage)
usage |= usage_flags::STORAGE;
return {
.imageType = vk::ImageType::e2D,
.format = format,
.extent = ToExtent3D(extent, 1),
.mipLevels = mipLevels,
.arrayLayers = 1,
.usage = usage,
};
}
vk::ImageCreateInfo
ToImageCreateInfo(const TextureCubeCreateInfo &createInfo)
{
auto &[format, side, name, isSampled, isMipMapped, isStorage] = createInfo;
WARN_IF(!IsPowerOfTwo(side), "ImageCube {1} is {0}x{0} (Non Power of Two)", side, name ? name : "<unnamed>");
const u8 mipLevels = isMipMapped ? 1 + Cast<u8>(floor(log2(side))) : 1;
auto usage = vk::ImageUsageFlags{};
if (isSampled)
usage |= usage_flags::SAMPLE;
if (isMipMapped)
usage |= usage_flags::MIPMAP;
if (isStorage)
usage |= usage_flags::STORAGE;
return {
.flags = vk::ImageCreateFlagBits::eCubeCompatible,
.imageType = vk::ImageType::e2D,
.format = format,
.extent = {side, side, 1},
.mipLevels = mipLevels,
.arrayLayers = 6,
.usage = usage,
};
}
vk::ImageCreateInfo
ToImageCreateInfo(const AttachmentCreateInfo &createInfo)
{
auto &[format, extent, name] = createInfo;
constexpr auto usage = usage_flags::COLOR_ATTACHMENT;
return {
.imageType = vk::ImageType::e2D,
.format = format,
.extent = ToExtent3D(extent, 1),
.mipLevels = 1,
.arrayLayers = 1,
.usage = usage,
};
}
vk::ImageCreateInfo
ToImageCreateInfo(const DepthStencilImageCreateInfo &createInfo)
{
auto &[extent, name] = createInfo;
constexpr vk::Format format = vk::Format::eD24UnormS8Uint;
constexpr auto usage = usage_flags::DEPTH_STENCIL_ATTACHMENT;
return {
.imageType = vk::ImageType::e2D,
.format = format,
.extent = ToExtent3D(extent, 1),
.mipLevels = 1,
.arrayLayers = 1,
.usage = usage,
};
}
ImageManager::ImageManager(const Device *device, const u32 maxCount, const u8 binding)
: Manager{device, maxCount, binding}
{
}

View File

@ -1,6 +0,0 @@
// =============================================
// Aster: manager.cpp
// Copyright (c) 2020-2025 Anish Bhobe
// =============================================
#include "systems/manager.h"

View File

@ -0,0 +1,375 @@
// =============================================
// Aster: pipeline_helpers.cpp
// Copyright (c) 2020-2025 Anish Bhobe
// =============================================
#include "aster/systems/rendering_device.h"
#include <aster/systems/pipeline_helpers.h>
using namespace aster;
using namespace aster::_internal;
struct WhatVisitor
{
std::string
operator()(std::monostate) const
{
return "No Error";
}
std::string
operator()(vk::Result result) const
{
return fmt::format("Vulkan Error: {}", result);
}
std::string
operator()(SlangResult result) const
{
return fmt::format("Slang Error: {}", result);
}
};
struct ValueVisitor
{
i32
operator()(std::monostate) const
{
return 0;
}
i32
operator()(vk::Result result) const
{
return static_cast<i32>(result);
}
i32
operator()(SlangResult result) const
{
return result;
}
};
i32
PipelineCreationError::Value()
{
return std::visit(ValueVisitor{}, m_Data);
}
PipelineCreationError::PipelineCreationError(vk::Result res)
: m_Data{res}
{
}
PipelineCreationError::PipelineCreationError(SlangResult res)
: m_Data{res}
{
}
PipelineCreationError::PipelineCreationError()
: m_Data{std::monostate{}}
{
}
PipelineCreationError::operator bool() const
{
return not std::holds_alternative<std::monostate>(m_Data);
}
std::string
PipelineCreationError::What()
{
return std::visit(WhatVisitor{}, m_Data);
}
vk::ShaderStageFlagBits
_internal::SlangToVulkanShaderStage(SlangStage const stage)
{
switch (stage)
{
case SLANG_STAGE_VERTEX:
return vk::ShaderStageFlagBits::eVertex;
case SLANG_STAGE_HULL:
return vk::ShaderStageFlagBits::eTessellationControl;
case SLANG_STAGE_DOMAIN:
return vk::ShaderStageFlagBits::eTessellationEvaluation;
case SLANG_STAGE_GEOMETRY:
return vk::ShaderStageFlagBits::eGeometry;
case SLANG_STAGE_FRAGMENT:
return vk::ShaderStageFlagBits::eFragment;
case SLANG_STAGE_COMPUTE:
return vk::ShaderStageFlagBits::eCompute;
case SLANG_STAGE_RAY_GENERATION:
return vk::ShaderStageFlagBits::eRaygenKHR;
case SLANG_STAGE_INTERSECTION:
return vk::ShaderStageFlagBits::eIntersectionKHR;
case SLANG_STAGE_ANY_HIT:
return vk::ShaderStageFlagBits::eAnyHitKHR;
case SLANG_STAGE_CLOSEST_HIT:
return vk::ShaderStageFlagBits::eClosestHitKHR;
case SLANG_STAGE_MISS:
return vk::ShaderStageFlagBits::eMissKHR;
case SLANG_STAGE_CALLABLE:
return vk::ShaderStageFlagBits::eCallableKHR;
case SLANG_STAGE_MESH:
return vk::ShaderStageFlagBits::eMeshEXT;
case SLANG_STAGE_AMPLIFICATION:
return vk::ShaderStageFlagBits::eTaskEXT;
case SLANG_STAGE_NONE:
case SLANG_STAGE_COUNT:
UNREACHABLE();
return {};
}
UNREACHABLE();
return {};
}
PipelineLayoutBuilder::PipelineLayoutBuilder(RenderingDevice *device, vk::DescriptorSetLayout bindlessLayout)
: m_Device{device}
, m_DescriptorSetLayouts{bindlessLayout} // if `null` will be filtered out during build.
{
}
vk::PipelineLayout
PipelineLayoutBuilder::Build()
{
vk::PipelineLayout pipelineLayout;
eastl::vector<vk::DescriptorSetLayout> filteredDescriptorSetLayouts;
filteredDescriptorSetLayouts.reserve(m_DescriptorSetLayouts.size());
for (auto dsl : m_DescriptorSetLayouts)
{
if (dsl)
{
filteredDescriptorSetLayouts.push_back(dsl);
}
}
vk::PipelineLayoutCreateInfo const createInfo = {
.setLayoutCount = static_cast<u32>(filteredDescriptorSetLayouts.size()),
.pSetLayouts = filteredDescriptorSetLayouts.data(),
.pushConstantRangeCount = static_cast<u32>(m_PushConstants.size()),
.pPushConstantRanges = m_PushConstants.data(),
};
AbortIfFailed(m_Device->m_Device->createPipelineLayout(&createInfo, nullptr, &pipelineLayout));
return pipelineLayout;
}
vk::DescriptorSetLayout
PipelineLayoutBuilder::CreateDescriptorSetLayout(vk::DescriptorSetLayoutCreateInfo const &createInfo) const
{
vk::DescriptorSetLayout dsl;
// Failure Cases are OoM errors. No recovery.
AbortIfFailed(m_Device->m_Device->createDescriptorSetLayout(&createInfo, nullptr, &dsl));
return dsl;
}
void
PipelineLayoutBuilder::AddDescriptorSetForParameterBlock(slang::TypeLayoutReflection *layout)
{
DescriptorLayoutBuilder descriptorLayoutBuilder{this};
descriptorLayoutBuilder.AddRangesForParamBlockElement(layout->getElementTypeLayout());
descriptorLayoutBuilder.Build();
}
void
PipelineLayoutBuilder::AddPushConstantRangeForConstantBuffer(slang::TypeLayoutReflection *layout)
{
auto const elementTypeLayout = layout->getElementTypeLayout();
auto const elementSize = elementTypeLayout->getSize();
if (elementSize == 0)
return;
m_PushConstants.push_back({
.stageFlags = m_Stage,
.offset = 0,
.size = static_cast<u32>(elementSize),
});
}
void
PipelineLayoutBuilder::AddSubObjectRange(slang::TypeLayoutReflection *layout, i64 subObjectRangeIndex)
{
auto bindingRangeIndex = layout->getSubObjectRangeBindingRangeIndex(subObjectRangeIndex);
switch (layout->getBindingRangeType(bindingRangeIndex))
{
case slang::BindingType::ParameterBlock: {
auto const parameterBlockTypeLayout = layout->getBindingRangeLeafTypeLayout(bindingRangeIndex);
AddDescriptorSetForParameterBlock(parameterBlockTypeLayout);
}
break;
case slang::BindingType::PushConstant: {
auto const constantBufferTypeLayout = layout->getBindingRangeLeafTypeLayout(bindingRangeIndex);
AddPushConstantRangeForConstantBuffer(constantBufferTypeLayout);
}
break;
default:
UNREACHABLE("Unexpected types");
}
}
vk::DescriptorType
BindingTypeToDescriptorType(slang::BindingType binding)
{
using vk::DescriptorType;
switch (binding)
{
case slang::BindingType::Sampler:
return DescriptorType::eSampler;
case slang::BindingType::Texture:
return DescriptorType::eSampledImage;
case slang::BindingType::ConstantBuffer:
return DescriptorType::eUniformBuffer;
case slang::BindingType::TypedBuffer:
return DescriptorType::eStorageBuffer;
case slang::BindingType::RawBuffer:
return DescriptorType::eStorageBuffer;
case slang::BindingType::CombinedTextureSampler:
return DescriptorType::eCombinedImageSampler;
case slang::BindingType::InlineUniformData:
return DescriptorType::eInlineUniformBlock;
case slang::BindingType::RayTracingAccelerationStructure:
return DescriptorType::eAccelerationStructureKHR;
case slang::BindingType::MutableTexture:
return DescriptorType::eStorageImage;
case slang::BindingType::MutableTypedBuffer:
return DescriptorType::eStorageBuffer;
case slang::BindingType::MutableRawBuffer:
return DescriptorType::eStorageBuffer;
default:
UNREACHABLE("Unsupported Types");
}
return {};
}
vk::ShaderStageFlags &
DescriptorLayoutBuilder::Stage() const
{
return m_PipelineLayoutBuilder->m_Stage;
}
DescriptorLayoutBuilder::DescriptorLayoutBuilder(PipelineLayoutBuilder *pipelineLayoutBuilder)
: m_PipelineLayoutBuilder{pipelineLayoutBuilder}
, m_SetIndex{static_cast<u32>(pipelineLayoutBuilder->m_DescriptorSetLayouts.size())}
{
m_PipelineLayoutBuilder->m_DescriptorSetLayouts.push_back();
}
void
DescriptorLayoutBuilder::AddDescriptorRange(slang::TypeLayoutReflection *layout, i64 const relativeSetIndex,
i64 const rangeIndex)
{
auto const bindingType = layout->getDescriptorSetDescriptorRangeType(relativeSetIndex, rangeIndex);
if (bindingType == slang::BindingType::PushConstant)
return;
u32 const descriptorCount =
static_cast<u32>(layout->getDescriptorSetDescriptorRangeDescriptorCount(relativeSetIndex, rangeIndex));
u32 const bindingIndex = static_cast<u32>(m_LayoutBindings.size());
auto const vkBindingType = BindingTypeToDescriptorType(bindingType);
m_LayoutBindings.push_back({
.binding = bindingIndex,
.descriptorType = vkBindingType,
.descriptorCount = descriptorCount,
.stageFlags = Stage(),
});
}
void
DescriptorLayoutBuilder::AddDescriptorRanges(slang::TypeLayoutReflection *layout)
{
i64 nSets = layout->getDescriptorSetCount();
for (i64 relativeSetIndex = 0; relativeSetIndex < nSets; ++relativeSetIndex)
{
i64 rangeCount = layout->getDescriptorSetDescriptorRangeCount(relativeSetIndex);
for (i64 rangeIndex = 0; rangeIndex < rangeCount; ++rangeIndex)
{
AddDescriptorRange(layout, relativeSetIndex, rangeIndex);
}
}
}
void
DescriptorLayoutBuilder::Build()
{
if (m_LayoutBindings.empty())
return;
auto const dsl = m_PipelineLayoutBuilder->CreateDescriptorSetLayout({
.bindingCount = static_cast<u32>(m_LayoutBindings.size()),
.pBindings = m_LayoutBindings.data(),
});
m_PipelineLayoutBuilder->m_DescriptorSetLayouts[m_SetIndex] = dsl;
}
void
DescriptorLayoutBuilder::AddAutomaticallyIntroducedUniformBuffer()
{
auto const vulkanBindingIndex = static_cast<u32>(m_LayoutBindings.size());
m_LayoutBindings.push_back({
.binding = vulkanBindingIndex,
.descriptorType = vk::DescriptorType::eUniformBuffer,
.descriptorCount = 1,
.stageFlags = vk::ShaderStageFlagBits::eAll,
});
}
void
DescriptorLayoutBuilder::AddRanges(slang::TypeLayoutReflection *layout)
{
AddDescriptorRanges(layout);
m_PipelineLayoutBuilder->AddSubObjectRanges(layout);
}
void
DescriptorLayoutBuilder::AddRangesForParamBlockElement(slang::TypeLayoutReflection *layout)
{
if (layout->getSize() > 0)
{
AddAutomaticallyIntroducedUniformBuffer();
}
AddRanges(layout);
}
void
DescriptorLayoutBuilder::AddGlobalScopeParameters(slang::ProgramLayout *layout)
{
Stage() = vk::ShaderStageFlagBits::eAll;
AddRangesForParamBlockElement(layout->getGlobalParamsTypeLayout());
}
void
DescriptorLayoutBuilder::AddEntryPointParameters(slang::ProgramLayout *layout)
{
u64 entryPointCount = layout->getEntryPointCount();
for (u64 i = 0; i < entryPointCount; ++i)
{
auto *entryPoint = layout->getEntryPointByIndex(i);
AddEntryPointParameters(entryPoint);
}
}
void
DescriptorLayoutBuilder::AddEntryPointParameters(slang::EntryPointLayout *layout)
{
Stage() = SlangToVulkanShaderStage(layout->getStage());
AddRangesForParamBlockElement(layout->getTypeLayout());
}
void
PipelineLayoutBuilder::AddSubObjectRanges(slang::TypeLayoutReflection *layout)
{
i64 subObjectRangeCount = layout->getSubObjectRangeCount();
for (i64 subObjectRangeIndex = 0; subObjectRangeIndex < subObjectRangeCount; ++subObjectRangeIndex)
{
AddSubObjectRange(layout, subObjectRangeIndex);
}
}

View File

@ -1,195 +0,0 @@
// =============================================
// Aster: render_resource_manager.cpp
// Copyright (c) 2020-2025 Anish Bhobe
// =============================================
#include "systems/render_resource_manager.h"
#include "EASTL/array.h"
#include "core/device.h"
#define AbortIfFailed(RESULT) \
do \
{ \
vk::Result _checkResultValue_; \
ERROR_IF(Failed(_checkResultValue_ = Cast<vk::Result>(RESULT)), "Cause: {}", _checkResultValue_) \
THEN_ABORT(_checkResultValue_); \
} while (false)
#define AbortIfFailedMV(RESULT, MSG, EXTRA) \
do \
{ \
vk::Result _checkResultValue_; \
ERROR_IF(Failed(_checkResultValue_ = Cast<vk::Result>(RESULT)), MSG " Cause: {}", EXTRA, _checkResultValue_) \
THEN_ABORT(_checkResultValue_); \
} while (false)
#define AbortIfFailedM(RESULT, MSG) \
do \
{ \
auto _checkResultValue_ = Cast<vk::Result>(RESULT); \
ERROR_IF(Failed(_checkResultValue_), MSG " Cause: {}", _checkResultValue_) THEN_ABORT(_checkResultValue_); \
} while (false)
using namespace systems;
u32
GetHandleInternal(concepts::HandleType auto &handle)
{
return *Recast<u32 *>(&handle);
}
RenderResourceManager::WriteOwner::WriteOwner(const Handle<Buffer> &handle)
: uBufferHandle(handle)
{
}
RenderResourceManager::WriteOwner::WriteOwner(const Handle<Image> &handle)
: uImageHandle(handle)
{
}
RenderResourceManager::RenderResourceManager(const Device *device, u32 const maxBuffers, const u32 maxImages)
: m_BufferManager{device, maxBuffers, BUFFER_BINDING_INDEX}
, m_ImageManager{device, maxImages, IMAGE_BINDING_INDEX}
{
eastl::array poolSizes = {
vk::DescriptorPoolSize{
.type = vk::DescriptorType::eStorageBuffer,
.descriptorCount = maxBuffers,
},
vk::DescriptorPoolSize{
.type = vk::DescriptorType::eCombinedImageSampler,
.descriptorCount = maxImages,
},
//vk::DescriptorPoolSize{
// .type = vk::DescriptorType::eStorageImage,
// .descriptorCount = storageTexturesCount,
//},
};
const vk::DescriptorPoolCreateInfo poolCreateInfo = {
.flags = vk::DescriptorPoolCreateFlagBits::eUpdateAfterBind,
.maxSets = 1,
.poolSizeCount = Cast<u32>(poolSizes.size()),
.pPoolSizes = poolSizes.data(),
};
AbortIfFailed(device->m_Device.createDescriptorPool(&poolCreateInfo, nullptr, &m_DescriptorPool));
eastl::array descriptorLayoutBindings = {
vk::DescriptorSetLayoutBinding{
.binding = BUFFER_BINDING_INDEX,
.descriptorType = vk::DescriptorType::eStorageBuffer,
.descriptorCount = Cast<u32>(maxBuffers),
.stageFlags = vk::ShaderStageFlagBits::eAll,
},
vk::DescriptorSetLayoutBinding{
.binding = IMAGE_BINDING_INDEX,
.descriptorType = vk::DescriptorType::eCombinedImageSampler,
.descriptorCount = Cast<u32>(maxImages),
.stageFlags = vk::ShaderStageFlagBits::eAll,
},
//vk::DescriptorSetLayoutBinding{
// .binding = STORAGE_TEXTURE_BINDING_INDEX,
// .descriptorType = vk::DescriptorType::eStorageImage,
// .descriptorCount = Cast<u32>(storageTexturesCount),
// .stageFlags = vk::ShaderStageFlagBits::eAll,
//},
};
vk::DescriptorBindingFlags bindingFlags =
vk::DescriptorBindingFlagBits::ePartiallyBound | vk::DescriptorBindingFlagBits::eUpdateAfterBind;
eastl::array<vk::DescriptorBindingFlags, decltype(descriptorLayoutBindings)::count> layoutBindingFlags;
layoutBindingFlags.fill(bindingFlags);
vk::DescriptorSetLayoutBindingFlagsCreateInfo bindingFlagsCreateInfo = {
.bindingCount = Cast<u32>(layoutBindingFlags.size()),
.pBindingFlags = layoutBindingFlags.data(),
};
static_assert(layoutBindingFlags.size() == descriptorLayoutBindings.size());
const vk::DescriptorSetLayoutCreateInfo descriptorSetLayoutCreateInfo = {
.pNext = &bindingFlagsCreateInfo,
.flags = vk::DescriptorSetLayoutCreateFlagBits::eUpdateAfterBindPool,
.bindingCount = Cast<u32>(descriptorLayoutBindings.size()),
.pBindings = descriptorLayoutBindings.data(),
};
AbortIfFailed(device->m_Device.createDescriptorSetLayout(&descriptorSetLayoutCreateInfo, nullptr, &m_SetLayout));
// One descriptor is enough. Updating it at any time is safe. (Update until submit, data held when pending)
// https://registry.khronos.org/vulkan/specs/1.3-extensions/man/html/VK_EXT_descriptor_indexing.html
// https://github.com/KhronosGroup/Vulkan-Guide/blob/main/chapters/extensions/VK_EXT_descriptor_indexing.adoc
const vk::DescriptorSetAllocateInfo descriptorSetAllocateInfo = {
.descriptorPool = m_DescriptorPool,
.descriptorSetCount = 1,
.pSetLayouts = &m_SetLayout,
};
AbortIfFailed(device->m_Device.allocateDescriptorSets(&descriptorSetAllocateInfo, &m_DescriptorSet));
device->SetName(m_SetLayout, "Bindless Layout");
device->SetName(m_DescriptorPool, "Bindless Pool");
device->SetName(m_DescriptorSet, "Bindless Set");
}
void
systems::RenderResourceManager::Commit(concepts::HandleType auto &handle)
{
using HandleType = decltype(handle)::Type;
if constexpr (std::is_same_v<HandleType, Buffer>)
{
const Buffer *buffer = handle.Fetch();
m_WriteInfos.emplace_back(vk::DescriptorBufferInfo{
.buffer = buffer->m_Buffer,
.offset = 0,
.range = buffer->GetSize(),
});
m_Writes.push_back({
.dstSet = m_DescriptorSet,
.dstBinding = BUFFER_BINDING_INDEX,
.dstArrayElement = handle.GetIndex(),
.descriptorCount = 1,
.descriptorType = vk::DescriptorType::eStorageBuffer,
.pBufferInfo = &m_WriteInfos.back().uBufferInfo,
});
}
else if constexpr (std::is_same_v<HandleType, Image>)
{
const Image *image = handle.Fetch();
m_WriteInfos.emplace_back(vk::DescriptorImageInfo{
.sampler = nullptr /* TODO Sampler */,
.imageView = image->m_View,
.imageLayout = vk::ImageLayout::eShaderReadOnlyOptimal,
});
m_Writes.push_back({
.dstSet = m_DescriptorSet,
.dstBinding = IMAGE_BINDING_INDEX,
.dstArrayElement = handle.GetIndex(),
.descriptorCount = 1,
.descriptorType = vk::DescriptorType::eSampledImage,
.pImageInfo = &m_WriteInfos.back().uImageInfo,
});
} else {
static_assert(false && "Type is currently unsupported");
}
m_WriteOwner.emplace_back(handle);
}
RenderResourceManager::WriteInfo::WriteInfo(const vk::DescriptorBufferInfo &info)
: uBufferInfo{info}
{
}
RenderResourceManager::WriteInfo::WriteInfo(const vk::DescriptorImageInfo &info)
: uImageInfo{info}
{
}
RenderResourceManager::WriteInfo::WriteInfo(const vk::BufferView &info)
: uBufferView{info}
{
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,151 @@
// =============================================
// Aster: sync_server.cpp
// Copyright (c) 2020-2025 Anish Bhobe
// =============================================
#include "aster/systems/sync_server.h"
#include "aster/systems/rendering_device.h"
using namespace aster;
using namespace aster::_internal;
SyncServer::Entry::Entry(RenderingDevice &device)
: m_CurrentPoint{0, 1}
, m_AttachedPool{nullptr}
{
constexpr static vk::SemaphoreTypeCreateInfo TYPE_CREATE_INFO = {
.semaphoreType = vk::SemaphoreType::eTimeline,
.initialValue = 0,
};
constexpr static vk::SemaphoreCreateInfo SEMAPHORE_CREATE_INFO = {.pNext = &TYPE_CREATE_INFO};
AbortIfFailed(device.m_Device->createSemaphore(&SEMAPHORE_CREATE_INFO, nullptr, &m_Semaphore));
}
void
SyncServer::Entry::Destroy(RenderingDevice &device)
{
if (m_Semaphore)
{
device.m_Device->destroy(Take(m_Semaphore), nullptr);
}
}
void
SyncServer::Entry::Wait(RenderingDevice &device)
{
vk::SemaphoreWaitInfo const waitInfo = {
.semaphoreCount = 1,
.pSemaphores = &m_Semaphore,
.pValues = &m_CurrentPoint.m_NextValue,
};
// This blocks.
// So `m_NextValue` is not modified while we wait for the signal.
AbortIfFailed(device.m_Device->waitSemaphores(&waitInfo, MaxValue<u64>));
// Thus, this is safe.
m_CurrentPoint.m_WaitValue = m_CurrentPoint.m_NextValue;
m_CurrentPoint.m_WaitValue = m_CurrentPoint.m_NextValue + 1;
if (m_AttachedPool)
{
m_AttachedPool->Reset();
m_AttachedPool = nullptr;
}
}
void
SyncServer::Entry::Next()
{
m_CurrentPoint.m_WaitValue = m_CurrentPoint.m_NextValue;
++m_CurrentPoint.m_NextValue;
}
void
SyncServer::Entry::AttachPool(ContextPool *pool)
{
assert(!m_AttachedPool);
m_AttachedPool = pool;
}
Receipt
SyncServer::Allocate()
{
auto &entry = AllocateEntry();
return Receipt{&entry};
}
void
SyncServer::Free(Receipt const receipt)
{
FreeEntry(GetEntry(receipt));
}
void
SyncServer::WaitOn(Receipt const receipt)
{
auto &entry = GetEntry(receipt);
entry.Wait(*m_Device);
FreeEntry(entry);
}
SyncServer::Entry &
SyncServer::AllocateEntry()
{
if (not m_FreeList.empty())
{
auto &alloc = m_FreeList.back();
m_FreeList.pop_back();
return alloc;
}
return m_Allocations.emplace_back(*m_Device);
}
void
SyncServer::FreeEntry(Entry &entry)
{
entry.Next();
m_FreeList.push_back(entry);
}
SyncServer::Entry &
SyncServer::GetEntry(Receipt receipt)
{
return *static_cast<Entry *>(receipt.m_Opaque);
}
SyncServer::SyncServer(RenderingDevice &device)
: m_Device{&device}
{
}
SyncServer::~SyncServer()
{
if (m_Device && !m_Allocations.empty())
{
for (auto &entry : m_Allocations)
{
entry.Destroy(*m_Device);
}
m_Device = nullptr;
}
}
SyncServer::SyncServer(SyncServer &&other) noexcept
: m_Device{Take(other.m_Device)}
, m_Allocations{std::move(other.m_Allocations)}
, m_FreeList{Take(other.m_FreeList)}
{
}
SyncServer &
SyncServer::operator=(SyncServer &&other) noexcept
{
if (this == &other)
return *this;
m_Device = Take(other.m_Device);
m_Allocations = std::move(other.m_Allocations);
m_FreeList = Take(other.m_FreeList);
return *this;
}

View File

@ -2,4 +2,4 @@
cmake_minimum_required(VERSION 3.13) cmake_minimum_required(VERSION 3.13)
target_sources(aster_core PRIVATE "logger.cpp") target_sources(aster_core PRIVATE "logger.cpp" "files.cpp")

View File

@ -0,0 +1,81 @@
// =============================================
// Aster: files.cpp
// Copyright (c) 2020-2024 Anish Bhobe
// =============================================
#include "aster/util/files.h"
using namespace aster;
eastl::vector<u32>
aster::ReadFile(std::string_view fileName)
{
FILE *filePtr = fopen(fileName.data(), "rb");
if (!filePtr)
{
ERROR("Invalid read of {}", fileName) THEN_ABORT(-1);
}
eastl::vector<u32> outputVec;
eastl::array<u32, 1024> buffer{};
usize totalRead = 0;
usize readCount;
do
{
readCount = fread(buffer.data(), sizeof(u32), buffer.size(), filePtr);
auto const nextSize = totalRead + readCount;
outputVec.resize(nextSize);
memcpy(outputVec.data() + totalRead, buffer.data(), readCount * sizeof *buffer.data());
totalRead = nextSize;
} while (readCount == buffer.size());
return outputVec;
}
eastl::vector<u8>
aster::ReadFileBytes(std::string_view fileName, bool errorOnFail)
{
FILE *filePtr = fopen(fileName.data(), "rb");
if (!filePtr)
{
ERROR_IF(errorOnFail, "Invalid open (r) of {}. Cause: {}", fileName, errno);
return {};
}
eastl::vector<u8> outputVec;
eastl::array<u8, 4096> buffer{};
usize totalRead = 0;
usize readCount;
do
{
readCount = fread(buffer.data(), sizeof(u8), buffer.size(), filePtr);
auto const nextSize = totalRead + readCount;
outputVec.resize(nextSize);
memcpy(outputVec.data() + totalRead, buffer.data(), readCount * sizeof *buffer.data());
totalRead = nextSize;
} while (readCount == buffer.size());
(void)fclose(filePtr);
return outputVec;
}
bool
aster::WriteFileBytes(std::string_view fileName, eastl::span<u8> const data)
{
FILE *filePtr = fopen(fileName.data(), "wb");
if (!filePtr)
{
ERROR("Invalid open (w) of {}. Cause: {}", fileName, errno);
return false;
}
usize const written = fwrite(data.data(), sizeof(u8), data.size(), filePtr);
(void)fclose(filePtr);
return written == data.size();
}

View File

@ -1,19 +1,19 @@
// ============================================= // =============================================
// Aster: logger.cpp // Aster: logger.cpp
// Copyright (c) 2020-2024 Anish Bhobe // Copyright (c) 2020-2025 Anish Bhobe
// ============================================= // =============================================
#include "util/logger.h" #include "aster/util/logger.h"
Logger g_Logger = Logger(); auto g_Logger = aster::Logger();
// ReSharper disable once CppInconsistentNaming // ReSharper disable once CppInconsistentNaming
/* Credits to Const-me */ /* Credits to Const-me */
namespace eastl namespace eastl
{ {
void void
AssertionFailure(const char *af) AssertionFailure(char const *af)
{ {
ERROR("{}", af); ERROR("{}", af);
} }
} // namespace eastl } // namespace eastl

View File

@ -1,31 +0,0 @@
#!/usr/bin/env bash
echo "Running CMake"
if grep 'NAME=NixOS' /etc/os-release
then
cmake --preset nixos
else
cmake --preset linux
fi
echo "Running Ninja"
if echo "$@" | grep -e "clean" -q
then
cmake --build build --target clean
elif echo "$@" | grep -e "rebuild" -q
then
cmake --build build --clean-first
else
cmake --build build
fi
if echo "$@" | grep -e "docs" -q
then
if echo "$@" | grep -e "-v" -q
then
doxygen
else
doxygen > /dev/null || echo "Doxygen Failed"
fi
fi

View File

@ -20,16 +20,16 @@
}, },
"nixpkgs": { "nixpkgs": {
"locked": { "locked": {
"lastModified": 1738734093, "lastModified": 1742976680,
"narHash": "sha256-UEYOKfXXKU49fR7dGB05As0s2pGbLK4xDo48Qtdm7xs=", "narHash": "sha256-Lcyi6YyR0PgN5rOrmM6mM/1MJIYhGi6rrq0+eiqvUb4=",
"owner": "NixOS", "owner": "kidrigger",
"repo": "nixpkgs", "repo": "nixpkgs",
"rev": "5b2753b0356d1c951d7a3ef1d086ba5a71fff43c", "rev": "51cf54bdbd9c1a0a2f833cced82451df0d9c25bd",
"type": "github" "type": "github"
}, },
"original": { "original": {
"owner": "NixOS", "owner": "kidrigger",
"ref": "nixpkgs-unstable", "ref": "imgui-docking",
"repo": "nixpkgs", "repo": "nixpkgs",
"type": "github" "type": "github"
} }

View File

@ -1,6 +1,6 @@
{ {
inputs = { inputs = {
nixpkgs.url = "github:NixOS/nixpkgs/nixpkgs-unstable"; nixpkgs.url = "github:kidrigger/nixpkgs/imgui-docking";
flake-utils.url = "github:numtide/flake-utils"; flake-utils.url = "github:numtide/flake-utils";
}; };
outputs = {self, nixpkgs, flake-utils }: outputs = {self, nixpkgs, flake-utils }:
@ -17,7 +17,7 @@
with pkgs; with pkgs;
{ {
devShells.default = clangStdenv.mkDerivation { devShells.default = clangStdenv.mkDerivation {
name = "BlazeEnv"; name = "Aster-Env";
nativeBuildInputs = [ nativeBuildInputs = [
@ -26,11 +26,10 @@
ccls ccls
clang-tools clang-tools
lldb lldb
(imgui.override {IMGUI_BUILD_VULKAN_BINDING = true; IMGUI_BUILD_GLFW_BINDING=true; }) (imgui.override {IMGUI_BUILD_VULKAN_BINDING = true; IMGUI_BUILD_GLFW_BINDING=true; IMGUI_EXPERIMENTAL_DOCKING = true; })
]; ];
buildInputs = [ buildInputs = [
sdl3
glm glm
glfw3 glfw3
eastl eastl
@ -50,6 +49,7 @@
directx-shader-compiler directx-shader-compiler
glslang glslang
shaderc shaderc
shader-slang
]; ];
}; };
} }

14
run.sh
View File

@ -1,14 +0,0 @@
#!/usr/bin/env bash
if [ -d "build" ]; then
pushd ./build/samples/04_scenes/ > /dev/null || exit
if echo "$@" | grep -e "debug" -q
then
lldb ./scene_render
else
./scene_render
fi
popd > /dev/null || exit
else
echo "Build Aster first."
fi

View File

@ -5,10 +5,10 @@ cmake_minimum_required(VERSION 3.13)
find_package(imgui CONFIG REQUIRED) find_package(imgui CONFIG REQUIRED)
add_library(util_helper STATIC add_library(util_helper STATIC
"helpers.h"
"helpers.cpp"
"frame.cpp"
"frame.h"
"gui.cpp" "gui.cpp"
"gui.h") "gui.h")

View File

@ -1,174 +0,0 @@
// =============================================
// Aster: frame.cpp
// Copyright (c) 2020-2024 Anish Bhobe
// =============================================
#include "frame.h"
#include "aster/core/device.h"
#include "aster/core/swapchain.h"
#include "helpers.h"
Frame::Frame(const Device *device, const u32 queueFamilyIndex, const u32 frameCount)
: m_FrameIdx(frameCount)
, m_ImageIdx(0)
{
m_Device = device;
eastl::fixed_string<char, 50, false> name = "Frame ";
name += Cast<char>('0' + frameCount);
const vk::CommandPoolCreateInfo commandPoolCreateInfo = {
.flags = vk::CommandPoolCreateFlagBits::eTransient,
.queueFamilyIndex = queueFamilyIndex,
};
AbortIfFailedMV(device->m_Device.createCommandPool(&commandPoolCreateInfo, nullptr, &m_Pool),
"Could not command pool for frame {}", frameCount);
constexpr vk::FenceCreateInfo fenceCreateInfo = {.flags = vk::FenceCreateFlagBits::eSignaled};
AbortIfFailedMV(device->m_Device.createFence(&fenceCreateInfo, nullptr, &m_FrameAvailableFence),
"Could not create a fence for frame {}", frameCount);
constexpr vk::SemaphoreCreateInfo semaphoreCreateInfo = {};
AbortIfFailedMV(device->m_Device.createSemaphore(&semaphoreCreateInfo, nullptr, &m_ImageAcquireSem),
"Could not create IA semaphore for frame {}.", frameCount);
AbortIfFailedMV(device->m_Device.createSemaphore(&semaphoreCreateInfo, nullptr, &m_RenderFinishSem),
"Could not create RF semaphore for frame {}.", frameCount);
const vk::CommandBufferAllocateInfo allocateInfo = {
.commandPool = m_Pool,
.level = vk::CommandBufferLevel::ePrimary,
.commandBufferCount = 1,
};
AbortIfFailed(m_Device->m_Device.allocateCommandBuffers(&allocateInfo, &m_CommandBuffer));
device->SetName(m_Pool, name.c_str());
device->SetName(m_FrameAvailableFence, name.c_str());
device->SetName(m_ImageAcquireSem, name.c_str());
device->SetName(m_RenderFinishSem, name.c_str());
device->SetName(m_CommandBuffer, name.c_str());
DEBUG("Frame {} created successfully.", frameCount);
}
void
Frame::Present(const vk::Queue commandQueue, Swapchain *swapchain, const Surface *surface, Size2D size)
{
vk::PresentInfoKHR presentInfo = {
.waitSemaphoreCount = 1,
.pWaitSemaphores = &m_RenderFinishSem,
.swapchainCount = 1,
.pSwapchains = &swapchain->m_Swapchain,
.pImageIndices = &m_ImageIdx,
.pResults = nullptr,
};
switch (auto result = commandQueue.presentKHR(&presentInfo))
{
case vk::Result::eSuccess:
break;
case vk::Result::eErrorOutOfDateKHR:
case vk::Result::eSuboptimalKHR:
DEBUG("Recreating Swapchain. Cause: {}", result);
swapchain->Create(surface, size);
break; // Present failed. We do nothing. Frame is skipped.
default:
AbortIfFailedM(result, "Swapchain Present failed.");
}
}
Frame::~Frame()
{
m_Device->m_Device.destroy(m_RenderFinishSem, nullptr);
m_Device->m_Device.destroy(m_ImageAcquireSem, nullptr);
m_Device->m_Device.destroy(m_FrameAvailableFence, nullptr);
m_Device->m_Device.destroy(m_Pool, nullptr);
DEBUG("Destoryed Frame");
}
Frame::Frame(Frame &&other) noexcept
: m_Device(other.m_Device)
, m_Pool(Take(other.m_Pool))
, m_CommandBuffer(Take(other.m_CommandBuffer))
, m_FrameAvailableFence(Take(other.m_FrameAvailableFence))
, m_ImageAcquireSem(Take(other.m_ImageAcquireSem))
, m_RenderFinishSem(Take(other.m_RenderFinishSem))
, m_FrameIdx(other.m_FrameIdx)
, m_ImageIdx(other.m_ImageIdx)
{
}
Frame &
Frame::operator=(Frame &&other) noexcept
{
if (this == &other)
return *this;
m_Device = other.m_Device;
m_Pool = Take(other.m_Pool);
m_CommandBuffer = Take(other.m_CommandBuffer);
m_FrameAvailableFence = Take(other.m_FrameAvailableFence);
m_ImageAcquireSem = Take(other.m_ImageAcquireSem);
m_RenderFinishSem = Take(other.m_RenderFinishSem);
m_FrameIdx = other.m_FrameIdx;
m_ImageIdx = other.m_ImageIdx;
return *this;
}
// Frame Manager
FrameManager::FrameManager(const Device *device, u32 queueFamilyIndex, u32 framesInFlight)
: m_Device(device)
, m_FramesInFlight(framesInFlight)
{
for (u32 i = 0; i < framesInFlight; ++i)
{
m_Frames.emplace_back(device, queueFamilyIndex, i);
}
}
Frame *
FrameManager::GetNextFrame(Swapchain *swapchain, const Surface *surface, Size2D size)
{
Frame *currentFrame = &m_Frames[m_CurrentFrameIdx];
u32 frameIndex = m_CurrentFrameIdx;
m_CurrentFrameIdx = (m_CurrentFrameIdx + 1) % m_FramesInFlight;
AbortIfFailedMV(m_Device->m_Device.waitForFences(1, &currentFrame->m_FrameAvailableFence, true, MaxValue<u64>),
"Waiting for fence {} failed.", frameIndex);
u32 imageIndex = 0;
bool imageAcquired = false;
while (!imageAcquired)
{
auto result = m_Device->m_Device.acquireNextImageKHR(swapchain->m_Swapchain, MaxValue<u64>,
currentFrame->m_ImageAcquireSem, nullptr, &imageIndex);
switch (result)
{
case vk::Result::eSuccess:
case vk::Result::eSuboptimalKHR: // Suboptimal can still render. Better to let this go for semaphores etc.
imageAcquired = true;
break; // Image acquired. Break out of loop.
case vk::Result::eErrorOutOfDateKHR:
DEBUG("Recreating Swapchain. Cause: {}", result);
swapchain->Create(surface, size);
break; // Image acquire has failed. We move to the next frame.
default:
AbortIfFailedMV(result, "Waiting for swapchain image {} failed.", frameIndex);
}
}
// Reset fences here. In case swapchain was out of date, we leave the fences signalled.
AbortIfFailedMV(m_Device->m_Device.resetFences(1, &currentFrame->m_FrameAvailableFence), "Fence {} reset failed.",
frameIndex);
AbortIfFailedMV(m_Device->m_Device.resetCommandPool(currentFrame->m_Pool, {}), "Command pool {} reset failed.",
frameIndex);
currentFrame->m_ImageIdx = imageIndex;
return currentFrame;
}

View File

@ -1,55 +0,0 @@
// =============================================
// Aster: frame.h
// Copyright (c) 2020-2024 Anish Bhobe
// =============================================
#pragma once
#include "aster/aster.h"
#include "helpers.h"
#include "aster/core/size.h"
#include <EASTL/fixed_vector.h>
struct Device;
struct Window;
struct Swapchain;
struct Surface;
struct Frame
{
// Persistent
const Device *m_Device;
vk::CommandPool m_Pool;
vk::CommandBuffer m_CommandBuffer;
vk::Fence m_FrameAvailableFence;
vk::Semaphore m_ImageAcquireSem;
vk::Semaphore m_RenderFinishSem;
u32 m_FrameIdx;
// Transient
u32 m_ImageIdx;
void Present(const vk::Queue commandQueue, Swapchain* swapchain, const Surface* surface, Size2D size);
Frame(const Device *device, u32 queueFamilyIndex, u32 frameCount);
~Frame();
Frame(Frame &&other) noexcept;
Frame &operator=(Frame &&other) noexcept;
DISALLOW_COPY_AND_ASSIGN(Frame);
};
struct FrameManager
{
const Device *m_Device;
eastl::fixed_vector<Frame, 4> m_Frames{};
u32 m_CurrentFrameIdx = 0;
u32 m_FramesInFlight = 0;
FrameManager(const Device *device, u32 queueFamilyIndex, u32 framesInFlight);
Frame *GetNextFrame(Swapchain *swapchain, const Surface *surface, Size2D size);
};

View File

@ -1,14 +1,11 @@
// ============================================= // =============================================
// Aster: gui.cpp // Aster: gui.cpp
// Copyright (c) 2020-2024 Anish Bhobe // Copyright (c) 2020-2025 Anish Bhobe
// ============================================= // =============================================
#include "gui.h" #include "gui.h"
#include "aster/core/context.h" #include "aster/aster.h"
#include "aster/core/device.h"
#include "aster/core/window.h"
#include "helpers.h"
#include <imgui_impl_glfw.h> #include <imgui_impl_glfw.h>
#include <imgui_impl_vulkan.h> #include <imgui_impl_vulkan.h>
@ -26,10 +23,11 @@ VulkanAssert(VkResult result)
} }
void void
Init(const Context *context, const Device *device, const Window *window, vk::Format attachmentFormat, Init(aster::RenderingDevice &device, aster::Window &window)
const u32 imageCount, const u32 queueFamily, const vk::Queue queue)
{ {
g_AttachmentFormat = attachmentFormat; using namespace aster;
g_AttachmentFormat = device.m_Swapchain.m_Format;
eastl::vector<vk::DescriptorPoolSize> poolSizes = { eastl::vector<vk::DescriptorPoolSize> poolSizes = {
{vk::DescriptorType::eSampler, 1000}, {vk::DescriptorType::eSampler, 1000},
@ -45,14 +43,14 @@ Init(const Context *context, const Device *device, const Window *window, vk::For
{vk::DescriptorType::eInputAttachment, 1000}, {vk::DescriptorType::eInputAttachment, 1000},
}; };
const vk::DescriptorPoolCreateInfo descriptorPoolCreateInfo = { vk::DescriptorPoolCreateInfo const descriptorPoolCreateInfo = {
.flags = vk::DescriptorPoolCreateFlagBits::eFreeDescriptorSet, .flags = vk::DescriptorPoolCreateFlagBits::eFreeDescriptorSet,
.maxSets = 1000, .maxSets = 1000,
.poolSizeCount = Cast<u32>(poolSizes.size()), .poolSizeCount = static_cast<u32>(poolSizes.size()),
.pPoolSizes = poolSizes.data(), .pPoolSizes = poolSizes.data(),
}; };
AbortIfFailed(device->m_Device.createDescriptorPool(&descriptorPoolCreateInfo, nullptr, &g_DescriptorPool)); AbortIfFailed(device.m_Device->createDescriptorPool(&descriptorPoolCreateInfo, nullptr, &g_DescriptorPool));
IMGUI_CHECKVERSION(); IMGUI_CHECKVERSION();
CreateContext(); CreateContext();
@ -63,22 +61,24 @@ Init(const Context *context, const Device *device, const Window *window, vk::For
StyleColorsDark(); StyleColorsDark();
ImGui_ImplGlfw_InitForVulkan(window->m_Window, true); ImGui_ImplGlfw_InitForVulkan(window.m_Window, true);
vk::PipelineRenderingCreateInfo renderingCreateInfo = { vk::PipelineRenderingCreateInfo renderingCreateInfo = {
.colorAttachmentCount = 1, .colorAttachmentCount = 1,
.pColorAttachmentFormats = &g_AttachmentFormat, .pColorAttachmentFormats = &g_AttachmentFormat,
}; };
// TODO: Switch this into being managed by RenderingDevice.
// m_Instance etc should private.
ImGui_ImplVulkan_InitInfo imguiVulkanInitInfo = { ImGui_ImplVulkan_InitInfo imguiVulkanInitInfo = {
.Instance = context->m_Instance, .Instance = device.m_Instance.m_Instance,
.PhysicalDevice = device->m_PhysicalDevice, .PhysicalDevice = device.m_Device.m_PhysicalDevice,
.Device = device->m_Device, .Device = device.m_Device.m_Device,
.QueueFamily = queueFamily, .QueueFamily = device.m_PrimaryQueueFamily,
.Queue = queue, .Queue = device.m_PrimaryQueue,
.DescriptorPool = g_DescriptorPool, .DescriptorPool = g_DescriptorPool,
.MinImageCount = imageCount, .MinImageCount = static_cast<u32>(device.m_Swapchain.m_Images.size()),
.ImageCount = imageCount, .ImageCount = static_cast<u32>(device.m_Swapchain.m_Images.size()),
.PipelineCache = nullptr, .PipelineCache = nullptr,
.UseDynamicRendering = true, .UseDynamicRendering = true,
.PipelineRenderingCreateInfo = renderingCreateInfo, .PipelineRenderingCreateInfo = renderingCreateInfo,
@ -91,14 +91,13 @@ Init(const Context *context, const Device *device, const Window *window, vk::For
} }
void void
Destroy(const Device *device) Destroy(aster::RenderingDevice const &device)
{ {
ImGui_ImplVulkan_Shutdown(); ImGui_ImplVulkan_Shutdown();
ImGui_ImplGlfw_Shutdown(); ImGui_ImplGlfw_Shutdown();
DestroyContext(); DestroyContext();
device->m_Device.destroy(Take(g_DescriptorPool), nullptr); device.m_Device->destroy(Take(g_DescriptorPool), nullptr);
} }
void void
@ -108,13 +107,13 @@ StartBuild()
ImGui_ImplGlfw_NewFrame(); ImGui_ImplGlfw_NewFrame();
NewFrame(); NewFrame();
static ImGuiDockNodeFlags dockspaceFlags = ImGuiDockNodeFlags_None | ImGuiDockNodeFlags_PassthruCentralNode; static ImGuiDockNodeFlags dockspaceFlags = ImGuiDockNodeFlags_None | ImGuiDockNodeFlags_PassthruCentralNode;
// We are using the ImGuiWindowFlags_NoDocking flag to make the parent window not dockable into, // We are using the ImGuiWindowFlags_NoDocking flag to make the parent window not dockable into,
// because it would be confusing to have two docking targets within each others. // because it would be confusing to have two docking targets within each others.
ImGuiWindowFlags windowFlags = ImGuiWindowFlags_None | ImGuiWindowFlags_NoDocking; ImGuiWindowFlags windowFlags = ImGuiWindowFlags_None | ImGuiWindowFlags_NoDocking;
const ImGuiViewport *viewport = GetMainViewport(); ImGuiViewport const *viewport = GetMainViewport();
SetNextWindowPos(viewport->WorkPos); SetNextWindowPos(viewport->WorkPos);
SetNextWindowSize(viewport->WorkSize); SetNextWindowSize(viewport->WorkSize);
// SetNextWindowViewport(viewport->ID); // SetNextWindowViewport(viewport->ID);
@ -130,18 +129,18 @@ StartBuild()
// all active windows docked into it will lose their parent and become undocked. // all active windows docked into it will lose their parent and become undocked.
// We cannot preserve the docking relationship between an active window and an inactive docking, otherwise // We cannot preserve the docking relationship between an active window and an inactive docking, otherwise
// any change of dockspace/settings would lead to windows being stuck in limbo and never being visible. // any change of dockspace/settings would lead to windows being stuck in limbo and never being visible.
PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0.0f, 0.0f)); PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0.0f, 0.0f));
Begin("DockSpace Demo", nullptr, windowFlags); Begin("DockSpace Demo", nullptr, windowFlags);
PopStyleVar(); PopStyleVar();
PopStyleVar(2); PopStyleVar(2);
// DockSpace // DockSpace
if (GetIO().ConfigFlags & ImGuiConfigFlags_DockingEnable) if (GetIO().ConfigFlags & ImGuiConfigFlags_DockingEnable)
{ {
const ImGuiID dockspaceId = GetID("MyDockSpace"); ImGuiID const dockspaceId = GetID("MyDockSpace");
DockSpace(dockspaceId, ImVec2(0.0f, 0.0f), dockspaceFlags); DockSpace(dockspaceId, ImVec2(0.0f, 0.0f), dockspaceFlags);
} }
} }
void void
@ -161,7 +160,7 @@ EndBuild()
} }
void void
Draw(const vk::CommandBuffer commandBuffer, const vk::Extent2D extent, const vk::ImageView view) Draw(vk::CommandBuffer const commandBuffer, vk::Extent2D const extent, vk::ImageView const view)
{ {
// OPTICK_EVENT(); // OPTICK_EVENT();
@ -181,7 +180,7 @@ Draw(const vk::CommandBuffer commandBuffer, const vk::Extent2D extent, const vk:
.clearValue = vk::ClearColorValue{0.0f, 0.0f, 0.0f, 1.0f}, .clearValue = vk::ClearColorValue{0.0f, 0.0f, 0.0f, 1.0f},
}; };
const vk::RenderingInfo renderingInfo = { vk::RenderingInfo const renderingInfo = {
.renderArea = {.extent = extent}, .renderArea = {.extent = extent},
.layerCount = 1, .layerCount = 1,
.colorAttachmentCount = 1, .colorAttachmentCount = 1,
@ -200,6 +199,36 @@ Draw(const vk::CommandBuffer commandBuffer, const vk::Extent2D extent, const vk:
#endif #endif
} }
void
Draw(aster::Frame &frame, aster::GraphicsContext &context)
{
context.BeginDebugRegion("UI Pass", {0.9f, 0.9f, 1.0f, 1.0f});
vk::RenderingAttachmentInfo attachmentInfo = {
.imageView = frame.m_SwapchainImageView,
.imageLayout = vk::ImageLayout::eColorAttachmentOptimal,
.resolveMode = vk::ResolveModeFlagBits::eNone,
.loadOp = vk::AttachmentLoadOp::eLoad,
.storeOp = vk::AttachmentStoreOp::eStore,
.clearValue = vk::ClearColorValue{0.0f, 0.0f, 0.0f, 1.0f},
};
vk::RenderingInfo const renderingInfo = {
.renderArea = {.extent = frame.m_SwapchainSize},
.layerCount = 1,
.colorAttachmentCount = 1,
.pColorAttachments = &attachmentInfo,
.pDepthAttachment = nullptr,
};
context.BeginRendering(renderingInfo);
ImGui_ImplVulkan_RenderDrawData(GetDrawData(), context.GetCommandBuffer());
context.EndRendering();
context.EndDebugRegion();
}
void void
PushDisable() PushDisable()
{ {
@ -213,4 +242,4 @@ PopDisable()
PopStyleVar(); PopStyleVar();
PopItemFlag(); PopItemFlag();
} }
} // namespace ImGui } // namespace ImGui

View File

@ -1,31 +1,36 @@
// ============================================= // =============================================
// Aster: gui.h // Aster: gui.h
// Copyright (c) 2020-2024 Anish Bhobe // Copyright (c) 2020-2025 Anish Bhobe
// ============================================= // =============================================
#pragma once #pragma once
#include "aster/aster.h" #include "aster/aster.h"
#include "aster/core/device.h"
#include <imgui.h> #include <imgui.h>
struct AttachmentImage; namespace aster
{
struct Device; struct Device;
struct Context; struct Instance;
struct Window; struct Window;
struct Swapchain; struct Swapchain;
class RenderingDevice;
class GraphicsContext;
struct Frame;
}
// ReSharper disable once CppInconsistentNaming // ReSharper disable once CppInconsistentNaming
namespace ImGui namespace ImGui
{ {
void Init(const Context *context, const Device *device, const Window *window, vk::Format attachmentFormat, void Init(aster::RenderingDevice &device, aster::Window &window);
u32 imageCount, u32 queueFamily, vk::Queue queue); void Destroy(const aster::RenderingDevice &device);
void Destroy(const Device *device);
void Recreate(); void Recreate();
void StartBuild(); void StartBuild();
void EndBuild(); void EndBuild();
void Draw(vk::CommandBuffer commandBuffer, vk::Extent2D extent, vk::ImageView view); void Draw(aster::Frame &frame, aster::GraphicsContext &context);
void PushDisable(); void PushDisable();
void PopDisable(); void PopDisable();

View File

@ -1,137 +0,0 @@
// =============================================
// Aster: helpers.cpp
// Copyright (c) 2020-2024 Anish Bhobe
// =============================================
#include "helpers.h"
#include "aster/core/device.h"
#include "aster/core/physical_device.h"
#include <EASTL/array.h>
constexpr QueueSupportFlags REQUIRED_QUEUE_SUPPORT = QueueSupportFlags{} | QueueSupportFlagBits::eGraphics |
QueueSupportFlagBits::eCompute | QueueSupportFlagBits::ePresent |
QueueSupportFlagBits::eTransfer;
bool
IsSuitableDevice(const PhysicalDevice *physicalDevice)
{
const bool hasAllRequiredQueues =
std::ranges::any_of(physicalDevice->m_QueueFamilies, [](const auto &queueFamilyProp) {
return (queueFamilyProp.m_Support & REQUIRED_QUEUE_SUPPORT) == REQUIRED_QUEUE_SUPPORT;
});
const bool isNotCpu = physicalDevice->m_DeviceProperties.deviceType != vk::PhysicalDeviceType::eCpu;
const bool hasPresentMode = !physicalDevice->m_PresentModes.empty();
const bool hasSurfaceFormat = !physicalDevice->m_SurfaceFormats.empty();
return hasSurfaceFormat && hasPresentMode && isNotCpu && hasAllRequiredQueues;
}
PhysicalDevice
FindSuitableDevice(const PhysicalDevices &physicalDevices)
{
for (auto &physicalDevice : physicalDevices)
{
if (IsSuitableDevice(&physicalDevice))
{
return physicalDevice;
}
}
ERROR("No suitable GPU available on the system.")
THEN_ABORT(vk::Result::eErrorUnknown);
}
QueueAllocation
FindAppropriateQueueAllocation(const PhysicalDevice *physicalDevice)
{
for (auto &queueFamilyInfo : physicalDevice->m_QueueFamilies)
{
if ((queueFamilyInfo.m_Support & REQUIRED_QUEUE_SUPPORT) == REQUIRED_QUEUE_SUPPORT)
{
return {
.m_Family = queueFamilyInfo.m_Index,
.m_Count = queueFamilyInfo.m_Count,
};
}
}
ERROR("No suitable queue family on the GPU.")
THEN_ABORT(vk::Result::eErrorUnknown);
}
eastl::vector<u32>
ReadFile(cstr fileName)
{
FILE *filePtr = fopen(fileName, "rb");
if (!filePtr)
{
ERROR("Invalid read of {}", fileName) THEN_ABORT(-1);
}
eastl::vector<u32> outputVec;
eastl::array<u32, 1024> buffer{};
usize totalRead = 0;
usize readCount;
do
{
readCount = fread(buffer.data(), sizeof(u32), buffer.size(), filePtr);
const auto nextSize = totalRead + readCount;
outputVec.resize(nextSize);
memcpy(outputVec.data() + totalRead, buffer.data(), readCount * sizeof *buffer.data());
totalRead = nextSize;
} while (readCount == buffer.size());
return outputVec;
}
eastl::vector<u8>
ReadFileBytes(cstr fileName, bool errorOnFail)
{
FILE *filePtr = fopen(fileName, "rb");
if (!filePtr)
{
ERROR_IF(errorOnFail, "Invalid open (r) of {}. Cause: {}", fileName, errno);
return {};
}
eastl::vector<u8> outputVec;
eastl::array<u8, 4096> buffer{};
usize totalRead = 0;
usize readCount;
do
{
readCount = fread(buffer.data(), sizeof(u8), buffer.size(), filePtr);
const auto nextSize = totalRead + readCount;
outputVec.resize(nextSize);
memcpy(outputVec.data() + totalRead, buffer.data(), readCount * sizeof *buffer.data());
totalRead = nextSize;
} while (readCount == buffer.size());
(void)fclose(filePtr);
return outputVec;
}
bool
WriteFileBytes(cstr fileName, eastl::span<u8> data)
{
FILE *filePtr = fopen(fileName, "wb");
if (!filePtr)
{
ERROR("Invalid open (w) of {}. Cause: {}", fileName, errno);
return false;
}
const usize written = fwrite(data.data(), sizeof(u8), data.size(), filePtr);
(void)fclose(filePtr);
return written == data.size();
}

View File

@ -1,49 +0,0 @@
// =============================================
// Aster: helpers.h
// Copyright (c) 2020-2024 Anish Bhobe
// =============================================
#pragma once
#include "aster/aster.h"
#include "aster/core/queue_allocation.h"
#include "EASTL/span.h"
#include <EASTL/vector.h>
#include <glm/gtc/matrix_transform.hpp>
struct PhysicalDevice;
class PhysicalDevices;
PhysicalDevice FindSuitableDevice(const PhysicalDevices &physicalDevices);
QueueAllocation FindAppropriateQueueAllocation(const PhysicalDevice *physicalDevice);
eastl::vector<u32> ReadFile(cstr fileName);
eastl::vector<u8> ReadFileBytes(cstr fileName, bool errorOnFail = true);
bool WriteFileBytes(cstr fileName, eastl::span<u8> data);
template <usize TSize>
using StackString = eastl::fixed_string<char, TSize, false>;
#define AbortIfFailed(RESULT) \
do \
{ \
vk::Result _checkResultValue_; \
ERROR_IF(Failed(_checkResultValue_ = Cast<vk::Result>(RESULT)), "Cause: {}", _checkResultValue_) \
THEN_ABORT(_checkResultValue_); \
} while (false)
#define AbortIfFailedMV(RESULT, MSG, EXTRA) \
do \
{ \
vk::Result _checkResultValue_; \
ERROR_IF(Failed(_checkResultValue_ = Cast<vk::Result>(RESULT)), MSG " Cause: {}", EXTRA, _checkResultValue_) \
THEN_ABORT(_checkResultValue_); \
} while (false)
#define AbortIfFailedM(RESULT, MSG) \
do \
{ \
auto _checkResultValue_ = Cast<vk::Result>(RESULT); \
ERROR_IF(Failed(_checkResultValue_), MSG " Cause: {}", _checkResultValue_) THEN_ABORT(_checkResultValue_); \
} while (false)

View File

@ -3,8 +3,8 @@
cmake_minimum_required(VERSION 3.13) cmake_minimum_required(VERSION 3.13)
add_executable(triangle "triangle.cpp") add_executable(triangle "triangle.cpp")
add_shader(triangle "shader/triangle.vert.glsl") add_shader(triangle "shader/triangle.slang")
add_shader(triangle "shader/triangle.frag.glsl") add_resource_dir(triangle "shader")
target_link_libraries(triangle PRIVATE aster_core) target_link_libraries(triangle PRIVATE aster_core)
target_link_libraries(triangle PRIVATE util_helper) target_link_libraries(triangle PRIVATE util_helper)

View File

@ -1,9 +0,0 @@
#version 450
#pragma shader_stage(fragment)
layout (location = 0) in vec3 inColor;
layout (location = 0) out vec4 outColor;
void main() {
outColor = vec4(inColor, 1.0);
}

View File

@ -0,0 +1,35 @@
struct Vertex {
float3 point;
float3 color;
};
struct VSIn {
Vertex vertex;
};
struct VSOut
{
float4 Pos : SV_POSITION;
float3 Color : COLOR0;
};
[shader("vertex")]
VSOut vsmain(VSIn input) {
VSOut output;
output.Pos = float4(input.vertex.point, 1.0f);
output.Color = input.vertex.color;
return output;
}
struct FSOut {
float4 Color;
};
[shader("fragment")]
FSOut fsmain(VSOut input) {
FSOut outp;
outp.Color = float4(input.Color, 1.0);
return outp;
}

View File

@ -1,27 +0,0 @@
#version 450
#pragma shader_stage(vertex)
layout(location=0) in vec4 position;
layout(location=1) in vec4 color;
layout(location=0) out vec3 outColor;
void main() {
/*
vec3 points[] = {
vec3(-0.5f, -0.5f, 0.0f),
vec3(0.5f, -0.5f, 0.0f),
vec3(0.0f, 0.5f, 0.0f)
};
vec3 colors[] = {
vec3( 1.0f, 0.0f, 0.0f ),
vec3( 0.0f, 1.0f, 0.0f ),
vec3( 0.0f, 0.0f, 1.0f ),
};
gl_Position = vec4(points[gl_VertexIndex], 1.0f);
outColor = vec3(colors[gl_VertexIndex]); //*/
//*
gl_Position = vec4(position.xyz, 1.0f);
outColor = vec3(color.rgb); //*/
}

View File

@ -1,28 +0,0 @@
struct VSIn {
int idx : SV_VERTEXID;
};
struct VSOut
{
float4 Pos : SV_POSITION;
[[vk::location(0)]] float3 Color : COLOR0;
};
VSOut main(VSIn input) {
float3 points[] = {
float3(-0.5f, -0.5f, 0.0f),
float3(0.5f, -0.5f, 0.0f),
float3(0.0f, 0.5f, 0.0f)
};
float3 colors[] = {
float3( 1.0f, 0.0f, 0.0f ),
float3( 0.0f, 1.0f, 0.0f ),
float3( 0.0f, 0.0f, 1.0f ),
};
VSOut output;
output.Pos = float4(points[input.idx], 1.0f);
output.Color = colors[input.idx];
return output;
}

View File

@ -1,115 +1,70 @@
// ============================================= // =============================================
// Aster: triangle.cpp // Aster: triangle.cpp
// Copyright (c) 2020-2024 Anish Bhobe // Copyright (c) 2020-2025 Anish Bhobe
// ============================================= // =============================================
#include "aster/aster.h" #include "aster/aster.h"
#include "aster/import_types.h"
#include "aster/core/buffer.h" #include "aster/util/files.h"
#include "aster/core/constants.h"
#include "aster/core/context.h"
#include "aster/core/device.h"
#include "aster/core/physical_device.h"
#include "aster/core/pipeline.h"
#include "aster/core/swapchain.h"
#include "aster/core/window.h"
#include "helpers.h"
#include <EASTL/array.h> #include <EASTL/array.h>
constexpr u32 MAX_FRAMES_IN_FLIGHT = 3; constexpr auto SHADER_MODULE = "triangle.slang";
constexpr auto VERTEX_SHADER_FILE = "shader/triangle.vert.glsl.spv";
constexpr auto FRAGMENT_SHADER_FILE = "shader/triangle.frag.glsl.spv";
vk::ShaderModule CreateShader(const Device *device, cstr shaderFile);
Pipeline CreatePipeline(const Device *device, const Swapchain *swapchain);
struct Vertex struct Vertex
{ {
vec3 m_Position; vec3 m_Position;
vec3 m_Color; vec3 m_Color;
constexpr static vk::VertexInputBindingDescription static eastl::vector<aster::AttributeInfo>
GetBinding(const u32 binding) GetAttributes()
{
return {.binding = binding, .stride = sizeof(Vertex), .inputRate = vk::VertexInputRate::eVertex};
}
constexpr static eastl::array<vk::VertexInputAttributeDescription, 2>
GetAttributes(const u32 binding)
{ {
return { return {
vk::VertexInputAttributeDescription{ {
.location = 0, .m_Location = 0,
.binding = binding, .m_Offset = offsetof(Vertex, m_Position),
.format = vk::Format::eR32G32B32Sfloat, .m_Format = aster::AttributeInfo::Format::eFloat32X3,
.offset = offsetof(Vertex, m_Position),
}, },
vk::VertexInputAttributeDescription{ {
.location = 1, .m_Location = 1,
.binding = binding, .m_Offset = offsetof(Vertex, m_Color),
.format = vk::Format::eR32G32B32Sfloat, .m_Format = aster::AttributeInfo::Format::eFloat32X3,
.offset = offsetof(Vertex, m_Color),
}, },
}; };
} }
}; };
struct Frame
{
const Device *m_Device;
vk::CommandPool m_Pool;
vk::CommandBuffer m_CommandBuffer;
vk::Fence m_FrameAvailableFence;
vk::Semaphore m_ImageAcquireSem;
vk::Semaphore m_RenderFinishSem;
Frame(const Device *device, u32 queueFamilyIndex, u32 frameCount);
~Frame();
};
int int
main(int, char **) main(int, char **)
{ {
using namespace aster;
MIN_LOG_LEVEL(Logger::LogType::eInfo); MIN_LOG_LEVEL(Logger::LogType::eInfo);
Window window = {"Triangle (Aster)", {640, 480}}; Window window = {"Triangle (Aster)", {640, 480}};
Context context = {"Triangle", VERSION}; RenderingDevice device{{
Surface surface = {&context, &window, "Primary"}; .m_Window = window,
.m_Features = {.m_Vulkan12Features = {.bufferDeviceAddress = true},
.m_Vulkan13Features = {.synchronization2 = true, .dynamicRendering = true}},
.m_AppName = "Triangle",
.m_ShaderSearchPaths = {"shader/"},
.m_UseBindless = false,
.m_Name = "Primary",
}};
PhysicalDevices physicalDevices = {&surface, &context}; Pipeline pipeline;
PhysicalDevice deviceToUse = FindSuitableDevice(physicalDevices); auto pipelineError = device.CreateGraphicsPipeline(pipeline, {
.m_VertexInputs = {{
INFO("Using {} as the primary device.", deviceToUse.m_DeviceProperties.deviceName.data()); .m_Attribute = Vertex::GetAttributes(),
.m_Stride = sizeof(Vertex),
Features enabledDeviceFeatures = { }},
.m_Vulkan12Features = {.bufferDeviceAddress = true}, .m_Shaders = {{
.m_Vulkan13Features = {.synchronization2 = true, .dynamicRendering = true}, .m_ShaderFile = SHADER_MODULE,
}; .m_EntryPoints = {"vsmain", "fsmain"},
QueueAllocation queueAllocation = FindAppropriateQueueAllocation(&deviceToUse); }},
Device device = {&context, &deviceToUse, &enabledDeviceFeatures, {queueAllocation}, "Primary Device"}; });
vk::Queue commandQueue = device.GetQueue(queueAllocation.m_Family, 0); ERROR_IF(pipelineError, "Error creating pipeline. Cause: {}", pipelineError.What());
Swapchain swapchain = {&surface, &device, window.GetSize(), "Primary Chain"};
Pipeline pipeline = CreatePipeline(&device, &swapchain);
vk::CommandPool copyPool;
vk::CommandBuffer copyBuffer;
{
vk::CommandPoolCreateInfo poolCreateInfo = {
.flags = vk::CommandPoolCreateFlagBits::eTransient,
.queueFamilyIndex = queueAllocation.m_Family,
};
auto result = device.m_Device.createCommandPool(&poolCreateInfo, nullptr, &copyPool);
ERROR_IF(Failed(result), "Copy command pool creation failed. Cause: {}", result) THEN_ABORT(result);
vk::CommandBufferAllocateInfo bufferAllocateInfo = {
.commandPool = copyPool,
.level = vk::CommandBufferLevel::ePrimary,
.commandBufferCount = 1,
};
result = device.m_Device.allocateCommandBuffers(&bufferAllocateInfo, &copyBuffer);
ERROR_IF(Failed(result), "Copy command buffer allocation failed. Cause: {}", result) THEN_ABORT(result);
}
// eastl::array<Vertex, 3> vertices{}; // eastl::array<Vertex, 3> vertices{};
eastl::array vertices = { eastl::array vertices = {
@ -117,60 +72,10 @@ main(int, char **)
Vertex{.m_Position = {0.5f, -0.5f, 0.0f}, .m_Color = {0.0f, 1.0f, 0.0f}}, Vertex{.m_Position = {0.5f, -0.5f, 0.0f}, .m_Color = {0.0f, 1.0f, 0.0f}},
Vertex{.m_Position = {0.0f, 0.5f, 0.0f}, .m_Color = {0.0f, 0.0f, 1.0f}}, Vertex{.m_Position = {0.0f, 0.5f, 0.0f}, .m_Color = {0.0f, 0.0f, 1.0f}},
}; };
VertexBuffer vbo; auto vbo = device.CreateVertexBuffer(vertices.size() * sizeof vertices[0], "VBO");
vbo.Init(&device, vertices.size() * sizeof vertices[0], "VBO"); vbo->Write(0, vertices.size() * sizeof vertices[0], vertices.data());
{
StagingBuffer staging;
staging.Init(&device, vertices.size() * sizeof vertices[0], "Staging");
staging.Write(&device, 0, vertices.size() * sizeof vertices[0], vertices.data());
vk::Fence fence;
vk::FenceCreateInfo fenceCreateInfo = {};
auto result = device.m_Device.createFence(&fenceCreateInfo, nullptr, &fence);
ERROR_IF(Failed(result), "Fence creation failed. Cause: {}", result) THEN_ABORT(result);
vk::CommandBufferBeginInfo beginInfo = {.flags = vk::CommandBufferUsageFlagBits::eOneTimeSubmit};
result = copyBuffer.begin(&beginInfo);
ERROR_IF(Failed(result), "Copy begin failed. Cause: {}", result) THEN_ABORT(result);
vk::BufferCopy bufferCopy = {.srcOffset = 0, .dstOffset = 0, .size = staging.GetSize()};
copyBuffer.copyBuffer(staging.m_Buffer, vbo.m_Buffer, 1, &bufferCopy);
result = copyBuffer.end();
ERROR_IF(Failed(result), "Copy end failed. Cause: {}", result) THEN_ABORT(result);
vk::SubmitInfo submitInfo = {
.commandBufferCount = 1,
.pCommandBuffers = &copyBuffer,
};
result = commandQueue.submit(1, &submitInfo, fence);
ERROR_IF(Failed(result), "Submit failed. Cause: {}", result) THEN_ABORT(result) ELSE_INFO("Submit copy");
result = device.m_Device.waitForFences(1, &fence, true, MaxValue<u64>);
ERROR_IF(Failed(result), "Fence wait failed. Cause: {}", result) THEN_ABORT(result) ELSE_INFO("Fence wait");
result = device.m_Device.resetCommandPool(copyPool, {});
ERROR_IF(Failed(result), "Couldn't reset command pool. Cause: {}", result) THEN_ABORT(result);
device.m_Device.destroy(fence, nullptr);
staging.Destroy(&device);
}
// Persistent variables // Persistent variables
vk::Viewport viewport = {
.x = 0,
.y = Cast<f32>(swapchain.m_Extent.height),
.width = Cast<f32>(swapchain.m_Extent.width),
.height = -Cast<f32>(swapchain.m_Extent.height),
.minDepth = 0.0,
.maxDepth = 1.0,
};
vk::Rect2D scissor = {
.offset = {0, 0},
.extent = swapchain.m_Extent,
};
vk::ImageSubresourceRange subresourceRange = { vk::ImageSubresourceRange subresourceRange = {
.aspectMask = vk::ImageAspectFlagBits::eColor, .aspectMask = vk::ImageAspectFlagBits::eColor,
@ -186,8 +91,8 @@ main(int, char **)
.dstAccessMask = vk::AccessFlagBits2::eColorAttachmentWrite, .dstAccessMask = vk::AccessFlagBits2::eColorAttachmentWrite,
.oldLayout = vk::ImageLayout::eUndefined, .oldLayout = vk::ImageLayout::eUndefined,
.newLayout = vk::ImageLayout::eColorAttachmentOptimal, .newLayout = vk::ImageLayout::eColorAttachmentOptimal,
.srcQueueFamilyIndex = queueAllocation.m_Family, .srcQueueFamilyIndex = vk::QueueFamilyIgnored,
.dstQueueFamilyIndex = queueAllocation.m_Family, .dstQueueFamilyIndex = vk::QueueFamilyIgnored,
.subresourceRange = subresourceRange, .subresourceRange = subresourceRange,
}; };
vk::DependencyInfo topOfThePipeDependency = { vk::DependencyInfo topOfThePipeDependency = {
@ -201,8 +106,8 @@ main(int, char **)
.dstAccessMask = vk::AccessFlagBits2::eNone, .dstAccessMask = vk::AccessFlagBits2::eNone,
.oldLayout = vk::ImageLayout::eColorAttachmentOptimal, .oldLayout = vk::ImageLayout::eColorAttachmentOptimal,
.newLayout = vk::ImageLayout::ePresentSrcKHR, .newLayout = vk::ImageLayout::ePresentSrcKHR,
.srcQueueFamilyIndex = queueAllocation.m_Family, .srcQueueFamilyIndex = vk::QueueFamilyIgnored,
.dstQueueFamilyIndex = queueAllocation.m_Family, .dstQueueFamilyIndex = vk::QueueFamilyIgnored,
.subresourceRange = subresourceRange, .subresourceRange = subresourceRange,
}; };
vk::DependencyInfo renderToPresentDependency = { vk::DependencyInfo renderToPresentDependency = {
@ -210,70 +115,39 @@ main(int, char **)
.pImageMemoryBarriers = &renderToPresentBarrier, .pImageMemoryBarriers = &renderToPresentBarrier,
}; };
// Frames
eastl::fixed_vector<Frame, MAX_FRAMES_IN_FLIGHT> frames;
for (u32 i = 0; i < MAX_FRAMES_IN_FLIGHT; ++i)
{
frames.emplace_back(&device, queueAllocation.m_Family, i);
}
INFO("Starting loop"); INFO("Starting loop");
u32 frameIndex = 0;
while (window.Poll()) while (window.Poll())
{ {
Frame *currentFrame = &frames[frameIndex]; aster::Frame &currentFrame = device.GetNextFrame();
auto result = device.m_Device.waitForFences(1, &currentFrame->m_FrameAvailableFence, true, MaxValue<u64>); Size2D swapchainSize = currentFrame.m_SwapchainSize;
ERROR_IF(Failed(result), "Waiting for fence {} failed. Cause: {}", frameIndex, result)
THEN_ABORT(result);
u32 imageIndex; vk::Viewport viewport = {
result = device.m_Device.acquireNextImageKHR(swapchain.m_Swapchain, MaxValue<u64>, .x = 0,
currentFrame->m_ImageAcquireSem, nullptr, &imageIndex); .y = static_cast<f32>(swapchainSize.m_Height),
if (Failed(result)) .width = static_cast<f32>(swapchainSize.m_Width),
{ .height = -static_cast<f32>(swapchainSize.m_Height),
switch (result) .minDepth = 0.0,
{ .maxDepth = 1.0,
case vk::Result::eErrorOutOfDateKHR: };
case vk::Result::eSuboptimalKHR:
INFO("Recreating Swapchain. Cause: {}", result);
swapchain.Create(&surface, window.GetSize());
viewport.y = Cast<f32>(swapchain.m_Extent.height);
viewport.width = Cast<f32>(swapchain.m_Extent.width);
viewport.height = -Cast<f32>(swapchain.m_Extent.height);
scissor.extent = swapchain.m_Extent;
continue; // Image acquire has failed. We move to the next frame.
default:
ERROR("Waiting for swapchain image {} failed. Cause: {}", frameIndex, result)
THEN_ABORT(result);
}
}
// Reset fences here. In case swapchain was out of date, we leave the fences signalled.
result = device.m_Device.resetFences(1, &currentFrame->m_FrameAvailableFence);
ERROR_IF(Failed(result), "Fence {} reset failed. Cause: {}", frameIndex, result)
THEN_ABORT(result);
result = device.m_Device.resetCommandPool(currentFrame->m_Pool, {}); vk::Rect2D scissor = {
ERROR_IF(Failed(result), "Command pool {} reset failed. Cause: {}", frameIndex, result) .offset = {0, 0},
THEN_ABORT(result); .extent = static_cast<vk::Extent2D>(swapchainSize),
};
vk::ImageView currentImageView = swapchain.m_ImageViews[imageIndex]; auto context = currentFrame.CreateGraphicsContext();
vk::Image currentImage = swapchain.m_Images[imageIndex];
vk::CommandBuffer cmd = currentFrame->m_CommandBuffer;
topOfThePipeBarrier.image = currentImage; topOfThePipeBarrier.image = currentFrame.m_SwapchainImage;
renderToPresentBarrier.image = currentImage; renderToPresentBarrier.image = currentFrame.m_SwapchainImage;
vk::CommandBufferBeginInfo beginInfo = {.flags = vk::CommandBufferUsageFlagBits::eOneTimeSubmit}; context.Begin();
result = cmd.begin(&beginInfo);
ERROR_IF(Failed(result), "Command buffer begin failed. Cause: {}", result)
THEN_ABORT(result);
cmd.pipelineBarrier2(&topOfThePipeDependency); context.Dependency(topOfThePipeDependency);
// Render // Render
vk::RenderingAttachmentInfo attachmentInfo = { vk::RenderingAttachmentInfo attachmentInfo = {
.imageView = currentImageView, .imageView = currentFrame.m_SwapchainImageView,
.imageLayout = vk::ImageLayout::eColorAttachmentOptimal, .imageLayout = vk::ImageLayout::eColorAttachmentOptimal,
.resolveMode = vk::ResolveModeFlagBits::eNone, .resolveMode = vk::ResolveModeFlagBits::eNone,
.loadOp = vk::AttachmentLoadOp::eClear, .loadOp = vk::AttachmentLoadOp::eClear,
@ -282,265 +156,29 @@ main(int, char **)
}; };
vk::RenderingInfo renderingInfo = { vk::RenderingInfo renderingInfo = {
.renderArea = {.extent = swapchain.m_Extent}, .renderArea = scissor,
.layerCount = 1, .layerCount = 1,
.colorAttachmentCount = 1, .colorAttachmentCount = 1,
.pColorAttachments = &attachmentInfo, .pColorAttachments = &attachmentInfo,
}; };
cmd.beginRendering(&renderingInfo); context.BeginRendering(renderingInfo);
cmd.setViewport(0, 1, &viewport); context.SetViewport(viewport);
cmd.setScissor(0, 1, &scissor); context.BindPipeline(pipeline);
cmd.bindPipeline(vk::PipelineBindPoint::eGraphics, pipeline.m_Pipeline); context.BindVertexBuffer(vbo);
usize offsets = 0; context.Draw(3);
cmd.bindVertexBuffers(0, 1, &vbo.m_Buffer, &offsets);
cmd.draw(3, 1, 0, 0);
cmd.endRendering(); context.EndRendering();
cmd.pipelineBarrier2(&renderToPresentDependency); context.Dependency(renderToPresentDependency);
result = cmd.end(); context.End();
ERROR_IF(Failed(result), "Command buffer end failed. Cause: {}", result)
THEN_ABORT(result);
vk::PipelineStageFlags waitDstStage = vk::PipelineStageFlagBits::eColorAttachmentOutput; device.Present(currentFrame, context);
vk::SubmitInfo submitInfo = {
.waitSemaphoreCount = 1,
.pWaitSemaphores = &currentFrame->m_ImageAcquireSem,
.pWaitDstStageMask = &waitDstStage,
.commandBufferCount = 1,
.pCommandBuffers = &cmd,
.signalSemaphoreCount = 1,
.pSignalSemaphores = &currentFrame->m_RenderFinishSem,
};
result = commandQueue.submit(1, &submitInfo, currentFrame->m_FrameAvailableFence);
ERROR_IF(Failed(result), "Command queue submit failed. Cause: {}", result)
THEN_ABORT(result);
vk::PresentInfoKHR presentInfo = {
.waitSemaphoreCount = 1,
.pWaitSemaphores = &currentFrame->m_RenderFinishSem,
.swapchainCount = 1,
.pSwapchains = &swapchain.m_Swapchain,
.pImageIndices = &imageIndex,
.pResults = nullptr,
};
result = commandQueue.presentKHR(&presentInfo);
if (Failed(result))
{
switch (result)
{
case vk::Result::eErrorOutOfDateKHR:
case vk::Result::eSuboptimalKHR:
INFO("Recreating Swapchain. Cause: {}", result);
swapchain.Create(&surface, window.GetSize());
viewport.y = Cast<f32>(swapchain.m_Extent.height);
viewport.width = Cast<f32>(swapchain.m_Extent.width);
viewport.height = -Cast<f32>(swapchain.m_Extent.height);
scissor.extent = swapchain.m_Extent;
break; // Present failed. We redo the frame.
default:
ERROR("Command queue present failed. Cause: {}", result)
THEN_ABORT(result);
}
}
frameIndex = (frameIndex + 1) % MAX_FRAMES_IN_FLIGHT;
} }
device.WaitIdle(); device.WaitIdle();
device.m_Device.destroy(copyPool, nullptr);
vbo.Destroy(&device);
return 0; return 0;
}
Frame::Frame(const Device *device, const u32 queueFamilyIndex, const u32 frameCount)
{
m_Device = device;
const vk::CommandPoolCreateInfo commandPoolCreateInfo = {
.flags = vk::CommandPoolCreateFlagBits::eTransient,
.queueFamilyIndex = queueFamilyIndex,
};
vk::Result result = device->m_Device.createCommandPool(&commandPoolCreateInfo, nullptr, &m_Pool);
ERROR_IF(Failed(result), "Could not command pool for frame {}. Cause: {}", frameCount, result)
THEN_ABORT(result);
constexpr vk::FenceCreateInfo fenceCreateInfo = {.flags = vk::FenceCreateFlagBits::eSignaled};
result = device->m_Device.createFence(&fenceCreateInfo, nullptr, &m_FrameAvailableFence);
ERROR_IF(Failed(result), "Could not create a fence for frame {}. Cause: {}", frameCount, result)
THEN_ABORT(result);
constexpr vk::SemaphoreCreateInfo semaphoreCreateInfo = {};
result = device->m_Device.createSemaphore(&semaphoreCreateInfo, nullptr, &m_ImageAcquireSem);
ERROR_IF(Failed(result), "Could not create IA semaphore for frame {}. Cause: {}", frameCount, result)
THEN_ABORT(result);
result = device->m_Device.createSemaphore(&semaphoreCreateInfo, nullptr, &m_RenderFinishSem);
ERROR_IF(Failed(result), "Could not create RF semaphore for frame {}. Cause: {}", frameCount, result)
THEN_ABORT(result);
const vk::CommandBufferAllocateInfo allocateInfo = {
.commandPool = m_Pool, .level = vk::CommandBufferLevel::ePrimary, .commandBufferCount = 1};
result = m_Device->m_Device.allocateCommandBuffers(&allocateInfo, &m_CommandBuffer);
ERROR_IF(Failed(result), "Command buffer allocation failed. Cause: {}", result)
THEN_ABORT(result);
DEBUG("Frame {} created successfully.", frameCount);
}
Pipeline
CreatePipeline(const Device *device, const Swapchain *swapchain)
{
// Pipeline Setup
auto vertexShaderModule = CreateShader(device, VERTEX_SHADER_FILE);
auto fragmentShaderModule = CreateShader(device, FRAGMENT_SHADER_FILE);
eastl::array<vk::PipelineShaderStageCreateInfo, 2> shaderStages = {{
{
.stage = vk::ShaderStageFlagBits::eVertex,
.module = vertexShaderModule,
.pName = "main",
},
{
.stage = vk::ShaderStageFlagBits::eFragment,
.module = fragmentShaderModule,
.pName = "main",
},
}};
vk::PipelineLayoutCreateInfo pipelineLayoutCreateInfo = {
.setLayoutCount = 0,
.pSetLayouts = nullptr,
.pushConstantRangeCount = 0,
.pPushConstantRanges = nullptr,
};
vk::PipelineLayout pipelineLayout;
vk::Result result = device->m_Device.createPipelineLayout(&pipelineLayoutCreateInfo, nullptr, &pipelineLayout);
ERROR_IF(Failed(result), "Could not create a pipeline layout. Cause: {}", result) THEN_ABORT(result);
device->SetName(pipelineLayout, "Triangle Layout");
vk::VertexInputBindingDescription inputBindingDescription = Vertex::GetBinding(0);
auto inputAttributeDescription = Vertex::GetAttributes(0);
vk::PipelineVertexInputStateCreateInfo vertexInputStateCreateInfo = {
.vertexBindingDescriptionCount = 1,
.pVertexBindingDescriptions = &inputBindingDescription,
.vertexAttributeDescriptionCount = Cast<u32>(inputAttributeDescription.size()),
.pVertexAttributeDescriptions = inputAttributeDescription.data(),
};
vk::PipelineInputAssemblyStateCreateInfo inputAssemblyStateCreateInfo = {
.topology = vk::PrimitiveTopology::eTriangleList,
.primitiveRestartEnable = false,
};
vk::PipelineViewportStateCreateInfo viewportStateCreateInfo = {
.viewportCount = 1,
.scissorCount = 1,
};
vk::PipelineRasterizationStateCreateInfo rasterizationStateCreateInfo = {
.depthClampEnable = false,
.rasterizerDiscardEnable = false,
.polygonMode = vk::PolygonMode::eFill,
.cullMode = vk::CullModeFlagBits::eNone,
.frontFace = vk::FrontFace::eCounterClockwise,
.depthBiasEnable = false,
.lineWidth = 1.0,
};
vk::PipelineMultisampleStateCreateInfo multisampleStateCreateInfo = {
.rasterizationSamples = vk::SampleCountFlagBits::e1,
.sampleShadingEnable = false,
};
vk::PipelineDepthStencilStateCreateInfo depthStencilStateCreateInfo = {
.depthTestEnable = false,
.depthWriteEnable = false,
};
vk::PipelineColorBlendAttachmentState colorBlendAttachmentState = {
.blendEnable = false,
.srcColorBlendFactor = vk::BlendFactor::eSrcColor,
.dstColorBlendFactor = vk::BlendFactor::eOneMinusSrcColor,
.colorBlendOp = vk::BlendOp::eAdd,
.srcAlphaBlendFactor = vk::BlendFactor::eSrcAlpha,
.dstAlphaBlendFactor = vk::BlendFactor::eOneMinusSrcAlpha,
.alphaBlendOp = vk::BlendOp::eAdd,
.colorWriteMask = vk::ColorComponentFlagBits::eR | vk::ColorComponentFlagBits::eG |
vk::ColorComponentFlagBits::eB | vk::ColorComponentFlagBits::eA,
};
vk::PipelineColorBlendStateCreateInfo colorBlendStateCreateInfo = {
.logicOpEnable = false,
.attachmentCount = 1,
.pAttachments = &colorBlendAttachmentState,
};
eastl::array dynamicStates = {
vk::DynamicState::eScissor,
vk::DynamicState::eViewport,
};
vk::PipelineDynamicStateCreateInfo dynamicStateCreateInfo = {
.dynamicStateCount = Cast<u32>(dynamicStates.size()),
.pDynamicStates = dynamicStates.data(),
};
vk::PipelineRenderingCreateInfo renderingCreateInfo = {
.viewMask = 0,
.colorAttachmentCount = 1,
.pColorAttachmentFormats = &swapchain->m_Format,
};
vk::GraphicsPipelineCreateInfo pipelineCreateInfo = {
.pNext = &renderingCreateInfo,
.stageCount = Cast<u32>(shaderStages.size()),
.pStages = shaderStages.data(),
.pVertexInputState = &vertexInputStateCreateInfo,
.pInputAssemblyState = &inputAssemblyStateCreateInfo,
.pViewportState = &viewportStateCreateInfo,
.pRasterizationState = &rasterizationStateCreateInfo,
.pMultisampleState = &multisampleStateCreateInfo,
.pDepthStencilState = &depthStencilStateCreateInfo,
.pColorBlendState = &colorBlendStateCreateInfo,
.pDynamicState = &dynamicStateCreateInfo,
.layout = pipelineLayout,
};
vk::Pipeline pipeline;
result = device->m_Device.createGraphicsPipelines(nullptr, 1, &pipelineCreateInfo, nullptr, &pipeline);
ERROR_IF(Failed(result), "Could not create a graphics pipeline. Cause: {}", result)
THEN_ABORT(result);
device->SetName(pipeline, "Triangle Pipeline");
device->m_Device.destroy(vertexShaderModule, nullptr);
device->m_Device.destroy(fragmentShaderModule, nullptr);
return {device, pipelineLayout, pipeline, {}};
}
vk::ShaderModule
CreateShader(const Device *device, cstr shaderFile)
{
eastl::vector<u32> shaderCode = ReadFile(shaderFile);
const vk::ShaderModuleCreateInfo shaderModuleCreateInfo = {
.codeSize = shaderCode.size() * sizeof(u32),
.pCode = shaderCode.data(),
};
vk::ShaderModule shaderModule;
vk::Result result = device->m_Device.createShaderModule(&shaderModuleCreateInfo, nullptr, &shaderModule);
ERROR_IF(Failed(result), "Shader {} could not be created. Cause: {}", shaderFile, result)
THEN_ABORT(result);
return shaderModule;
}
Frame::~Frame()
{
m_Device->m_Device.destroy(m_RenderFinishSem, nullptr);
m_Device->m_Device.destroy(m_ImageAcquireSem, nullptr);
m_Device->m_Device.destroy(m_FrameAvailableFence, nullptr);
m_Device->m_Device.destroy(m_Pool, nullptr);
DEBUG("Destoryed Frame");
} }

View File

@ -5,10 +5,8 @@ cmake_minimum_required(VERSION 3.13)
#set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fsanitize=undefined -fsanitize=address") #set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fsanitize=undefined -fsanitize=address")
add_executable(box "box.cpp" "stb_image.h") add_executable(box "box.cpp" "stb_image.h")
add_shader(box "shader/box.vert.glsl") add_shader(box "shader/box.slang")
add_shader(box "shader/box.frag.glsl") add_resource_dir(box "shader/")
add_shader(box "shader/box.vs.hlsl")
add_shader(box "shader/box.ps.hlsl")
target_link_libraries(box PRIVATE aster_core) target_link_libraries(box PRIVATE aster_core)
target_link_libraries(box PRIVATE util_helper) target_link_libraries(box PRIVATE util_helper)

View File

@ -1,33 +1,31 @@
// ============================================= // =============================================
// Aster: box.cpp // Aster: box.cpp
// Copyright (c) 2020-2024 Anish Bhobe // Copyright (c) 2020-2025 Anish Bhobe
// ============================================= // =============================================
#include "aster/aster.h" #include "aster/aster.h"
#include "aster/core/buffer.h" #include "aster/core/buffer.h"
#include "aster/core/constants.h" #include "aster/core/constants.h"
#include "aster/core/context.h"
#include "aster/core/device.h"
#include "aster/core/image.h" #include "aster/core/image.h"
#include "aster/core/physical_device.h" #include "aster/core/physical_device.h"
#include "aster/core/pipeline.h" #include "aster/core/pipeline.h"
#include "aster/core/swapchain.h" #include "aster/core/swapchain.h"
#include "aster/core/window.h" #include "aster/core/window.h"
#include "helpers.h"
#define STB_IMAGE_IMPLEMENTATION #define STB_IMAGE_IMPLEMENTATION
#include "aster/systems/buffer_manager.h" #include "aster/systems/commit_manager.h"
#include "aster/systems/image_manager.h" #include "aster/systems/rendering_device.h"
#include "frame.h" #include "aster/util/files.h"
#include "stb_image.h" #include "stb_image.h"
#include <EASTL/array.h> #include <EASTL/array.h>
constexpr u32 MAX_FRAMES_IN_FLIGHT = 3;
constexpr auto VERTEX_SHADER_FILE = "shader/box.vs.hlsl.spv"; constexpr auto VERTEX_SHADER_FILE = "shader/box.vs.hlsl.spv";
constexpr auto FRAGMENT_SHADER_FILE = "shader/box.ps.hlsl.spv"; constexpr auto FRAGMENT_SHADER_FILE = "shader/box.ps.hlsl.spv";
constexpr auto SHADER_FILE = "box";
using namespace aster;
struct ImageFile struct ImageFile
{ {
@ -38,6 +36,12 @@ struct ImageFile
bool Load(cstr fileName); bool Load(cstr fileName);
[[nodiscard]] usize GetSize() const; [[nodiscard]] usize GetSize() const;
operator eastl::span<u8>() const
{
return {static_cast<u8 *>(m_Data), GetSize()};
}
~ImageFile(); ~ImageFile();
}; };
@ -63,7 +67,7 @@ ImageFile::Load(cstr fileName)
usize usize
ImageFile::GetSize() const ImageFile::GetSize() const
{ {
return Cast<usize>(m_Width) * m_Height * m_NumChannels; return static_cast<usize>(m_Width) * m_Height * m_NumChannels;
} }
ImageFile::~ImageFile() ImageFile::~ImageFile()
@ -72,9 +76,6 @@ ImageFile::~ImageFile()
m_Data = nullptr; m_Data = nullptr;
} }
vk::ShaderModule CreateShader(const Device *device, cstr shaderFile);
Pipeline CreatePipeline(const Device *device, const Swapchain *swapchain);
struct Vertex struct Vertex
{ {
vec3 m_Position; vec3 m_Position;
@ -93,86 +94,55 @@ struct Camera
int int
main(int, char **) main(int, char **)
{ {
MIN_LOG_LEVEL(Logger::LogType::eInfo); MIN_LOG_LEVEL(Logger::LogType::eDebug);
Window window = {"Box (Aster)", {640, 480}}; Window window = {"Box (Aster)", {640, 480}};
Context context = {"Box", VERSION};
Surface surface = {&context, &window, "Primary"};
PhysicalDevices physicalDevices = {&surface, &context};
PhysicalDevice deviceToUse = FindSuitableDevice(physicalDevices);
INFO("Using {} as the primary device.", deviceToUse.m_DeviceProperties.deviceName.data());
Features enabledDeviceFeatures = { Features enabledDeviceFeatures = {
.m_Vulkan10Features = {.samplerAnisotropy = true}, .m_Vulkan10Features = {.samplerAnisotropy = true},
.m_Vulkan12Features = {.bufferDeviceAddress = true}, .m_Vulkan12Features =
{
.descriptorIndexing = true,
.shaderSampledImageArrayNonUniformIndexing = true,
.shaderStorageBufferArrayNonUniformIndexing = true,
.shaderStorageImageArrayNonUniformIndexing = true,
.descriptorBindingUniformBufferUpdateAfterBind = true, // Not related to Bindless
.descriptorBindingSampledImageUpdateAfterBind = true,
.descriptorBindingStorageImageUpdateAfterBind = true,
.descriptorBindingStorageBufferUpdateAfterBind = true,
.descriptorBindingPartiallyBound = true,
.runtimeDescriptorArray = true,
.timelineSemaphore = true,
.bufferDeviceAddress = true,
.bufferDeviceAddressCaptureReplay = true,
},
.m_Vulkan13Features = {.synchronization2 = true, .dynamicRendering = true}, .m_Vulkan13Features = {.synchronization2 = true, .dynamicRendering = true},
}; };
QueueAllocation queueAllocation = FindAppropriateQueueAllocation(&deviceToUse);
Device device = {&context, &deviceToUse, &enabledDeviceFeatures, {queueAllocation}, "Primary Device"};
vk::Queue commandQueue = device.GetQueue(queueAllocation.m_Family, 0);
Swapchain swapchain = {&surface, &device, window.GetSize(), "Primary Chain"};
Pipeline pipeline = CreatePipeline(&device, &swapchain);
systems::BufferManager bufferManager{&device, 12, 0}; RenderingDevice device{{
systems::ImageManager imageManager{&device, 12, 1}; .m_Window = window,
.m_Features = enabledDeviceFeatures,
.m_AppName = "Box",
.m_AppVersion = VERSION,
.m_ShaderSearchPaths = {"shader/"},
}};
Pipeline pipeline;
auto pipelineResult =
device.CreateGraphicsPipeline(pipeline, {.m_Shaders = {
{.m_ShaderFile = SHADER_FILE, .m_EntryPoints = {"vsmain", "fsmain"}},
}});
ERROR_IF(pipelineResult, "Could not create pipeline. Cause: {}", pipelineResult.What())
THEN_ABORT(pipelineResult.Value());
auto swapchainSize = device.GetSwapchainSize();
Camera camera = { Camera camera = {
.m_Model = {1.0f}, .m_Model = {1.0f},
.m_View = glm::lookAt(vec3(0.0f, 2.0f, 2.0f), vec3(0.0f), vec3(0.0f, 1.0f, 0.0f)), .m_View = lookAt(vec3(0.0f, 2.0f, 2.0f), vec3(0.0f), vec3(0.0f, 1.0f, 0.0f)),
.m_Perspective = glm::perspective( .m_Perspective = glm::perspective(
70_deg, Cast<f32>(swapchain.m_Extent.width) / Cast<f32>(swapchain.m_Extent.height), 0.1f, 100.0f), 70_deg, static_cast<f32>(swapchainSize.m_Width) / static_cast<f32>(swapchainSize.m_Height), 0.1f, 100.0f),
}; };
vk::DescriptorPool descriptorPool;
vk::DescriptorSet descriptorSet;
{
vk::DescriptorSetLayout descriptorSetLayout = pipeline.m_SetLayouts.front();
eastl::array poolSizes = {
vk::DescriptorPoolSize{
.type = vk::DescriptorType::eUniformBuffer,
.descriptorCount = 1,
},
vk::DescriptorPoolSize{
.type = vk::DescriptorType::eCombinedImageSampler,
.descriptorCount = 1,
},
vk::DescriptorPoolSize{
.type = vk::DescriptorType::eStorageBuffer,
.descriptorCount = 1,
},
};
vk::DescriptorPoolCreateInfo descriptorPoolCreateInfo = {
.maxSets = 1, .poolSizeCount = Cast<u32>(poolSizes.size()), .pPoolSizes = poolSizes.data()};
AbortIfFailed(device.m_Device.createDescriptorPool(&descriptorPoolCreateInfo, nullptr, &descriptorPool));
vk::DescriptorSetAllocateInfo descriptorSetAllocateInfo = {
.descriptorPool = descriptorPool,
.descriptorSetCount = 1,
.pSetLayouts = &descriptorSetLayout,
};
AbortIfFailed(device.m_Device.allocateDescriptorSets(&descriptorSetAllocateInfo, &descriptorSet));
}
vk::CommandPool copyPool;
vk::CommandBuffer copyBuffer;
{
vk::CommandPoolCreateInfo poolCreateInfo = {
.flags = vk::CommandPoolCreateFlagBits::eTransient,
.queueFamilyIndex = queueAllocation.m_Family,
};
AbortIfFailedM(device.m_Device.createCommandPool(&poolCreateInfo, nullptr, &copyPool),
"Copy command pool creation failed.");
vk::CommandBufferAllocateInfo bufferAllocateInfo = {
.commandPool = copyPool,
.level = vk::CommandBufferLevel::ePrimary,
.commandBufferCount = 1,
};
AbortIfFailedM(device.m_Device.allocateCommandBuffers(&bufferAllocateInfo, &copyBuffer),
"Copy command buffer allocation failed.");
}
eastl::array vertices = { eastl::array vertices = {
Vertex{.m_Position = vec3(0.5f, 0.5f, -0.5f), .m_TexCoord0 = vec2(1.0f, 1.0f)}, Vertex{.m_Position = vec3(0.5f, 0.5f, -0.5f), .m_TexCoord0 = vec2(1.0f, 1.0f)},
Vertex{.m_Position = vec3(0.5f, -0.5f, -0.5f), .m_TexCoord0 = vec2(1.0f, 0.0f)}, Vertex{.m_Position = vec3(0.5f, -0.5f, -0.5f), .m_TexCoord0 = vec2(1.0f, 0.0f)},
@ -222,21 +192,19 @@ main(int, char **)
assert(loaded); assert(loaded);
INFO("Image {}x{} : {} channels", imageFile.m_Width, imageFile.m_Height, imageFile.m_NumChannels); INFO("Image {}x{} : {} channels", imageFile.m_Width, imageFile.m_Height, imageFile.m_NumChannels);
auto vbo = bufferManager.CreateStorageBuffer(vertices.size() * sizeof vertices[0], "Vertex Buffer").ToPointer(); auto vbo = device.CreateStorageBuffer(vertices.size() * sizeof vertices[0], "Vertex Buffer");
auto crate = imageManager vbo->Write(0, vertices.size() * sizeof vertices[0], vertices.data());
.CreateTexture2D({
.m_Format = vk::Format::eR8G8B8A8Srgb, auto crate = device.CreateTexture2DWithView({
.m_Extent = {imageFile.m_Width, imageFile.m_Height}, .m_Format = vk::Format::eR8G8B8A8Srgb,
.m_Name = "Crate Texture", .m_Extent = {imageFile.m_Width, imageFile.m_Height},
}) .m_Name = "Crate Texture",
.ToPointer(); });
vbo->Write(&device, 0, vertices.size() * sizeof vertices[0], vertices.data());
{ {
StagingBuffer imageStaging;
imageStaging.Init(&device, imageFile.GetSize(), "Image Staging"); auto imageStaging = device.CreateStagingBuffer(imageFile.GetSize(), "Image Staging");
imageStaging.Write(&device, 0, imageFile.GetSize(), imageFile.m_Data); imageStaging->Write(0, imageFile.GetSize(), imageFile.m_Data);
vk::ImageMemoryBarrier2 imageReadyToWrite = { vk::ImageMemoryBarrier2 imageReadyToWrite = {
.srcStageMask = vk::PipelineStageFlagBits2::eTransfer, .srcStageMask = vk::PipelineStageFlagBits2::eTransfer,
@ -245,9 +213,9 @@ main(int, char **)
.dstAccessMask = vk::AccessFlagBits2::eTransferWrite, .dstAccessMask = vk::AccessFlagBits2::eTransferWrite,
.oldLayout = vk::ImageLayout::eUndefined, .oldLayout = vk::ImageLayout::eUndefined,
.newLayout = vk::ImageLayout::eTransferDstOptimal, .newLayout = vk::ImageLayout::eTransferDstOptimal,
.srcQueueFamilyIndex = queueAllocation.m_Family, .srcQueueFamilyIndex = vk::QueueFamilyIgnored,
.dstQueueFamilyIndex = queueAllocation.m_Family, .dstQueueFamilyIndex = vk::QueueFamilyIgnored,
.image = crate->m_Image, .image = crate->GetImage(),
.subresourceRange = .subresourceRange =
{ {
.aspectMask = vk::ImageAspectFlagBits::eColor, .aspectMask = vk::ImageAspectFlagBits::eColor,
@ -269,9 +237,9 @@ main(int, char **)
.dstAccessMask = vk::AccessFlagBits2::eShaderRead, .dstAccessMask = vk::AccessFlagBits2::eShaderRead,
.oldLayout = vk::ImageLayout::eTransferDstOptimal, .oldLayout = vk::ImageLayout::eTransferDstOptimal,
.newLayout = vk::ImageLayout::eShaderReadOnlyOptimal, .newLayout = vk::ImageLayout::eShaderReadOnlyOptimal,
.srcQueueFamilyIndex = queueAllocation.m_Family, .srcQueueFamilyIndex = device.m_TransferQueueFamily,
.dstQueueFamilyIndex = queueAllocation.m_Family, .dstQueueFamilyIndex = device.m_PrimaryQueueFamily,
.image = crate->m_Image, .image = crate->GetImage(),
.subresourceRange = .subresourceRange =
{ {
.aspectMask = vk::ImageAspectFlagBits::eColor, .aspectMask = vk::ImageAspectFlagBits::eColor,
@ -286,140 +254,25 @@ main(int, char **)
.pImageMemoryBarriers = &imageReadyToRead, .pImageMemoryBarriers = &imageReadyToRead,
}; };
vk::Fence fence; auto context = device.CreateTransferContext();
vk::FenceCreateInfo fenceCreateInfo = {}; context.Begin();
AbortIfFailed(device.m_Device.createFence(&fenceCreateInfo, nullptr, &fence));
vk::CommandBufferBeginInfo beginInfo = {.flags = vk::CommandBufferUsageFlagBits::eOneTimeSubmit}; context.Dependency(imageReadyToWriteDependency);
AbortIfFailed(copyBuffer.begin(&beginInfo));
copyBuffer.pipelineBarrier2(&imageReadyToWriteDependency); context.UploadTexture(crate->m_Image, imageFile);
vk::BufferImageCopy imageCopy = { context.Dependency(imageReadyToReadDependency);
.bufferOffset = 0,
.bufferRowLength = imageFile.m_Width,
.bufferImageHeight = imageFile.m_Height,
.imageSubresource =
{
.aspectMask = vk::ImageAspectFlagBits::eColor,
.mipLevel = 0,
.baseArrayLayer = 0,
.layerCount = 1,
},
.imageOffset = {},
.imageExtent = {imageFile.m_Width, imageFile.m_Height, 1},
};
copyBuffer.copyBufferToImage(imageStaging.m_Buffer, crate->m_Image, vk::ImageLayout::eTransferDstOptimal, 1,
&imageCopy);
copyBuffer.pipelineBarrier2(&imageReadyToReadDependency); context.End();
AbortIfFailed(copyBuffer.end()); auto recpt = device.Submit(context);
device.WaitOn(recpt);
vk::SubmitInfo submitInfo = {
.commandBufferCount = 1,
.pCommandBuffers = &copyBuffer,
};
AbortIfFailed(commandQueue.submit(1, &submitInfo, fence));
INFO("Submit copy");
AbortIfFailed(device.m_Device.waitForFences(1, &fence, true, MaxValue<u64>));
INFO("Fence wait");
AbortIfFailedM(device.m_Device.resetCommandPool(copyPool, {}), "Couldn't reset command pool.");
device.m_Device.destroy(fence, nullptr);
imageStaging.Destroy(&device);
} }
vk::Sampler sampler; auto ubo = device.CreateStorageBuffer(sizeof camera, "Camera UBO");
{ ubo->Write(0, sizeof camera, &camera);
vk::SamplerCreateInfo samplerCreateInfo = {
.magFilter = vk::Filter::eLinear,
.minFilter = vk::Filter::eLinear,
.mipmapMode = vk::SamplerMipmapMode::eLinear,
.addressModeU = vk::SamplerAddressMode::eRepeat,
.addressModeV = vk::SamplerAddressMode::eRepeat,
.addressModeW = vk::SamplerAddressMode::eRepeat,
.mipLodBias = 0.2f,
.anisotropyEnable = true,
.maxAnisotropy = 1.0f,
.compareEnable = false,
.minLod = 0,
.maxLod = 4,
.unnormalizedCoordinates = false,
};
AbortIfFailed(device.m_Device.createSampler(&samplerCreateInfo, nullptr, &sampler));
}
auto ubo = bufferManager.CreateUniformBuffer(sizeof camera, "Camera UBO").ToPointer();
ubo->Write(&device, 0, sizeof camera, &camera);
vk::DescriptorBufferInfo descriptorBufferInfo = {
.buffer = ubo->m_Buffer,
.offset = 0,
.range = ubo->GetSize(),
};
vk::DescriptorImageInfo descriptorImageInfo = {
.sampler = sampler,
.imageView = crate->m_View,
.imageLayout = vk::ImageLayout::eShaderReadOnlyOptimal,
};
vk::DescriptorBufferInfo descriptorStorageBufferInfo = {
.buffer = vbo->m_Buffer,
.offset = 0,
.range = vbo->GetSize(),
};
eastl::array writeDescriptors = {
vk::WriteDescriptorSet{
.dstSet = descriptorSet,
.dstBinding = 0,
.dstArrayElement = 0,
.descriptorCount = 1,
.descriptorType = vk::DescriptorType::eUniformBuffer,
.pBufferInfo = &descriptorBufferInfo,
},
vk::WriteDescriptorSet{
.dstSet = descriptorSet,
.dstBinding = 1,
.dstArrayElement = 0,
.descriptorCount = 1,
.descriptorType = vk::DescriptorType::eCombinedImageSampler,
.pImageInfo = &descriptorImageInfo,
},
vk::WriteDescriptorSet{
.dstSet = descriptorSet,
.dstBinding = 2,
.dstArrayElement = 0,
.descriptorCount = 1,
.descriptorType = vk::DescriptorType::eStorageBuffer,
.pBufferInfo = &descriptorStorageBufferInfo,
},
};
device.m_Device.updateDescriptorSets(Cast<u32>(writeDescriptors.size()), writeDescriptors.data(), 0, nullptr);
// Persistent variables // Persistent variables
vk::Viewport viewport = {
.x = 0,
.y = Cast<f32>(swapchain.m_Extent.height),
.width = Cast<f32>(swapchain.m_Extent.width),
.height = -Cast<f32>(swapchain.m_Extent.height),
.minDepth = 0.0,
.maxDepth = 1.0,
};
vk::Rect2D scissor = {
.offset = {0, 0},
.extent = swapchain.m_Extent,
};
auto resizeViewportScissor = [&viewport, &scissor](vk::Extent2D extent) {
viewport.y = Cast<f32>(extent.height);
viewport.width = Cast<f32>(extent.width);
viewport.height = -Cast<f32>(extent.height);
scissor.extent = extent;
};
swapchain.RegisterResizeCallback(resizeViewportScissor);
vk::ImageSubresourceRange subresourceRange = { vk::ImageSubresourceRange subresourceRange = {
.aspectMask = vk::ImageAspectFlagBits::eColor, .aspectMask = vk::ImageAspectFlagBits::eColor,
@ -437,8 +290,8 @@ main(int, char **)
.dstAccessMask = vk::AccessFlagBits2::eColorAttachmentWrite, .dstAccessMask = vk::AccessFlagBits2::eColorAttachmentWrite,
.oldLayout = vk::ImageLayout::eUndefined, .oldLayout = vk::ImageLayout::eUndefined,
.newLayout = vk::ImageLayout::eColorAttachmentOptimal, .newLayout = vk::ImageLayout::eColorAttachmentOptimal,
.srcQueueFamilyIndex = queueAllocation.m_Family, .srcQueueFamilyIndex = vk::QueueFamilyIgnored,
.dstQueueFamilyIndex = queueAllocation.m_Family, .dstQueueFamilyIndex = vk::QueueFamilyIgnored,
.subresourceRange = subresourceRange, .subresourceRange = subresourceRange,
}; };
vk::DependencyInfo topOfThePipeDependency = { vk::DependencyInfo topOfThePipeDependency = {
@ -452,8 +305,8 @@ main(int, char **)
.dstAccessMask = vk::AccessFlagBits2::eNone, .dstAccessMask = vk::AccessFlagBits2::eNone,
.oldLayout = vk::ImageLayout::eColorAttachmentOptimal, .oldLayout = vk::ImageLayout::eColorAttachmentOptimal,
.newLayout = vk::ImageLayout::ePresentSrcKHR, .newLayout = vk::ImageLayout::ePresentSrcKHR,
.srcQueueFamilyIndex = queueAllocation.m_Family, .srcQueueFamilyIndex = vk::QueueFamilyIgnored,
.dstQueueFamilyIndex = queueAllocation.m_Family, .dstQueueFamilyIndex = vk::QueueFamilyIgnored,
.subresourceRange = subresourceRange, .subresourceRange = subresourceRange,
}; };
vk::DependencyInfo renderToPresentDependency = { vk::DependencyInfo renderToPresentDependency = {
@ -461,50 +314,85 @@ main(int, char **)
.pImageMemoryBarriers = &renderToPresentBarrier, .pImageMemoryBarriers = &renderToPresentBarrier,
}; };
FrameManager frameManager = {&device, queueAllocation.m_Family, MAX_FRAMES_IN_FLIGHT}; eastl::fixed_vector<Ref<ImageView>, MAX_FRAMES_IN_FLIGHT> depthImages;
eastl::fixed_vector<Ref<Image>, MAX_FRAMES_IN_FLIGHT> depthImages;
auto initDepthImages = [&imageManager, &depthImages, &frameManager] (const vk::Extent2D extent) { auto initDepthImages = [&depthImages, &device](vk::Extent2D const extent) {
for (u32 i = 0; i < frameManager.m_FramesInFlight; ++i) for (u32 i = 0; i < MAX_FRAMES_IN_FLIGHT; ++i)
{ {
depthImages.push_back( depthImages.push_back(device.CreateDepthStencilImageWithView({.m_Extent = extent, .m_Name = "Depth"}));
imageManager.CreateDepthStencilImage({.m_Extent = extent, .m_Name = "Depth"}).ToPointer());
} }
}; };
initDepthImages(swapchain.m_Extent); initDepthImages(swapchainSize);
auto recreateDepthBuffers = [&depthImages, &initDepthImages](const vk::Extent2D extent) { auto recreateDepthBuffers = [&depthImages, &initDepthImages](vk::Extent2D const extent) {
depthImages.clear(); depthImages.clear();
initDepthImages(extent); initDepthImages(extent);
}; };
swapchain.RegisterResizeCallback(recreateDepthBuffers);
struct PCB
{
uptr m_VertexBuffer;
uptr m_Camera;
ResId<TextureView> m_Texture;
};
static_assert(sizeof(PCB) == 24);
auto &commitManager = CommitManager::Instance();
PCB pcb = {
.m_VertexBuffer = vbo->GetDeviceAddress(),
.m_Camera = ubo->GetDeviceAddress(),
.m_Texture = commitManager.CommitTexture(crate),
};
Time::Init(); Time::Init();
auto prevSwapchainSize = swapchainSize;
INFO("Starting loop"); INFO("Starting loop");
while (window.Poll()) while (window.Poll())
{ {
Time::Update(); Time::Update();
camera.m_Model *= rotate(mat4{1.0f}, Cast<f32>(45.0_deg * Time::m_Delta), vec3(0.0f, 1.0f, 0.0f)); camera.m_Model *= rotate(mat4{1.0f}, static_cast<f32>(45.0_deg * Time::m_Delta), vec3(0.0f, 1.0f, 0.0f));
ubo->Write(&device, 0, sizeof camera, &camera); ubo->Write(0, sizeof camera, &camera);
Frame *currentFrame = frameManager.GetNextFrame(&swapchain, &surface, window.GetSize()); auto &currentFrame = device.GetNextFrame();
u32 imageIndex = currentFrame->m_ImageIdx; prevSwapchainSize = swapchainSize;
vk::ImageView currentImageView = swapchain.m_ImageViews[imageIndex]; swapchainSize = currentFrame.m_SwapchainSize;
vk::Image currentImage = swapchain.m_Images[imageIndex]; if (swapchainSize != prevSwapchainSize)
vk::CommandBuffer cmd = currentFrame->m_CommandBuffer; {
vk::ImageView currentDepthImageView = depthImages[currentFrame->m_FrameIdx]->m_View; recreateDepthBuffers(swapchainSize);
}
vk::Viewport viewport = {
.x = 0,
.y = static_cast<f32>(swapchainSize.m_Height),
.width = static_cast<f32>(swapchainSize.m_Width),
.height = -static_cast<f32>(swapchainSize.m_Height),
.minDepth = 0.0,
.maxDepth = 1.0,
};
vk::Rect2D scissor = {
.offset = {0, 0},
.extent = static_cast<vk::Extent2D>(swapchainSize),
};
vk::ImageView currentImageView = currentFrame.m_SwapchainImageView;
vk::Image currentImage = currentFrame.m_SwapchainImage;
vk::ImageView currentDepthImageView = depthImages[currentFrame.m_FrameIdx]->m_View;
topOfThePipeBarrier.image = currentImage; topOfThePipeBarrier.image = currentImage;
renderToPresentBarrier.image = currentImage; renderToPresentBarrier.image = currentImage;
vk::CommandBufferBeginInfo beginInfo = {.flags = vk::CommandBufferUsageFlagBits::eOneTimeSubmit}; auto context = currentFrame.CreateGraphicsContext();
AbortIfFailed(cmd.begin(&beginInfo));
cmd.pipelineBarrier2(&topOfThePipeDependency); context.Begin();
context.Dependency(topOfThePipeDependency);
// Render // Render
eastl::array attachmentInfos = { eastl::array attachmentInfos = {
@ -528,207 +416,30 @@ main(int, char **)
}; };
vk::RenderingInfo renderingInfo = { vk::RenderingInfo renderingInfo = {
.renderArea = {.extent = swapchain.m_Extent}, .renderArea = scissor,
.layerCount = 1, .layerCount = 1,
.colorAttachmentCount = Cast<u32>(attachmentInfos.size()), .colorAttachmentCount = static_cast<u32>(attachmentInfos.size()),
.pColorAttachments = attachmentInfos.data(), .pColorAttachments = attachmentInfos.data(),
.pDepthAttachment = &depthAttachment, .pDepthAttachment = &depthAttachment,
}; };
cmd.beginRendering(&renderingInfo); context.BeginRendering(renderingInfo);
cmd.setViewport(0, 1, &viewport); context.SetViewport(viewport);
cmd.setScissor(0, 1, &scissor); context.BindPipeline(pipeline);
cmd.bindPipeline(vk::PipelineBindPoint::eGraphics, pipeline.m_Pipeline); context.PushConstantBlock(pcb);
cmd.bindDescriptorSets(vk::PipelineBindPoint::eGraphics, pipeline.m_Layout, 0, 1, &descriptorSet, 0, nullptr); context.Draw(vertices.size());
cmd.draw(Cast<u32>(vertices.size()), 1, 0, 0);
cmd.endRendering(); context.EndRendering();
cmd.pipelineBarrier2(&renderToPresentDependency); context.Dependency(renderToPresentDependency);
AbortIfFailed(cmd.end()); context.End();
vk::PipelineStageFlags waitDstStage = vk::PipelineStageFlagBits::eColorAttachmentOutput; device.Present(currentFrame, context);
vk::SubmitInfo submitInfo = {
.waitSemaphoreCount = 1,
.pWaitSemaphores = &currentFrame->m_ImageAcquireSem,
.pWaitDstStageMask = &waitDstStage,
.commandBufferCount = 1,
.pCommandBuffers = &cmd,
.signalSemaphoreCount = 1,
.pSignalSemaphores = &currentFrame->m_RenderFinishSem,
};
AbortIfFailed(commandQueue.submit(1, &submitInfo, currentFrame->m_FrameAvailableFence));
currentFrame->Present(commandQueue, &swapchain, &surface, window.GetSize());
} }
device.WaitIdle(); device.WaitIdle();
device.m_Device.destroy(sampler, nullptr);
device.m_Device.destroy(descriptorPool, nullptr);
device.m_Device.destroy(copyPool, nullptr);
return 0; return 0;
}
Pipeline
CreatePipeline(const Device *device, const Swapchain *swapchain)
{
// Pipeline Setup
auto vertexShaderModule = CreateShader(device, VERTEX_SHADER_FILE);
auto fragmentShaderModule = CreateShader(device, FRAGMENT_SHADER_FILE);
eastl::array<vk::PipelineShaderStageCreateInfo, 2> shaderStages = {{
{
.stage = vk::ShaderStageFlagBits::eVertex,
.module = vertexShaderModule,
.pName = "main",
},
{
.stage = vk::ShaderStageFlagBits::eFragment,
.module = fragmentShaderModule,
.pName = "main",
},
}};
eastl::array descriptorSetLayoutBinding = {
vk::DescriptorSetLayoutBinding{
.binding = 0,
.descriptorType = vk::DescriptorType::eUniformBuffer,
.descriptorCount = 1,
.stageFlags = vk::ShaderStageFlagBits::eVertex,
},
vk::DescriptorSetLayoutBinding{
.binding = 1,
.descriptorType = vk::DescriptorType::eCombinedImageSampler,
.descriptorCount = 1,
.stageFlags = vk::ShaderStageFlagBits::eFragment,
},
vk::DescriptorSetLayoutBinding{
.binding = 2,
.descriptorType = vk::DescriptorType::eStorageBuffer,
.descriptorCount = 1,
.stageFlags = vk::ShaderStageFlagBits::eVertex,
},
};
vk::DescriptorSetLayoutCreateInfo descriptorSetLayoutCreateInfo = {
.bindingCount = Cast<u32>(descriptorSetLayoutBinding.size()),
.pBindings = descriptorSetLayoutBinding.data(),
};
vk::DescriptorSetLayout descriptorSetLayout;
AbortIfFailed(
device->m_Device.createDescriptorSetLayout(&descriptorSetLayoutCreateInfo, nullptr, &descriptorSetLayout));
vk::PipelineLayoutCreateInfo pipelineLayoutCreateInfo = {
.setLayoutCount = 1,
.pSetLayouts = &descriptorSetLayout,
.pushConstantRangeCount = 0,
.pPushConstantRanges = nullptr,
};
vk::PipelineLayout pipelineLayout;
AbortIfFailed(device->m_Device.createPipelineLayout(&pipelineLayoutCreateInfo, nullptr, &pipelineLayout));
device->SetName(pipelineLayout, "Box Layout");
vk::PipelineVertexInputStateCreateInfo vertexInputStateCreateInfo = {};
vk::PipelineInputAssemblyStateCreateInfo inputAssemblyStateCreateInfo = {
.topology = vk::PrimitiveTopology::eTriangleList,
.primitiveRestartEnable = false,
};
vk::PipelineViewportStateCreateInfo viewportStateCreateInfo = {
.viewportCount = 1,
.scissorCount = 1,
};
vk::PipelineRasterizationStateCreateInfo rasterizationStateCreateInfo = {
.depthClampEnable = false,
.rasterizerDiscardEnable = false,
.polygonMode = vk::PolygonMode::eFill,
.cullMode = vk::CullModeFlagBits::eNone,
.frontFace = vk::FrontFace::eCounterClockwise,
.depthBiasEnable = false,
.lineWidth = 1.0,
};
vk::PipelineMultisampleStateCreateInfo multisampleStateCreateInfo = {
.rasterizationSamples = vk::SampleCountFlagBits::e1,
.sampleShadingEnable = false,
};
vk::PipelineDepthStencilStateCreateInfo depthStencilStateCreateInfo = {
.depthTestEnable = true,
.depthWriteEnable = true,
.depthCompareOp = vk::CompareOp::eLess,
};
vk::PipelineColorBlendAttachmentState colorBlendAttachmentState = {
.blendEnable = false,
.srcColorBlendFactor = vk::BlendFactor::eSrcColor,
.dstColorBlendFactor = vk::BlendFactor::eOneMinusSrcColor,
.colorBlendOp = vk::BlendOp::eAdd,
.srcAlphaBlendFactor = vk::BlendFactor::eSrcAlpha,
.dstAlphaBlendFactor = vk::BlendFactor::eOneMinusSrcAlpha,
.alphaBlendOp = vk::BlendOp::eAdd,
.colorWriteMask = vk::ColorComponentFlagBits::eR | vk::ColorComponentFlagBits::eG |
vk::ColorComponentFlagBits::eB | vk::ColorComponentFlagBits::eA,
};
vk::PipelineColorBlendStateCreateInfo colorBlendStateCreateInfo = {
.logicOpEnable = false,
.attachmentCount = 1,
.pAttachments = &colorBlendAttachmentState,
};
eastl::array dynamicStates = {
vk::DynamicState::eScissor,
vk::DynamicState::eViewport,
};
vk::PipelineDynamicStateCreateInfo dynamicStateCreateInfo = {
.dynamicStateCount = Cast<u32>(dynamicStates.size()),
.pDynamicStates = dynamicStates.data(),
};
vk::PipelineRenderingCreateInfo renderingCreateInfo = {
.viewMask = 0,
.colorAttachmentCount = 1,
.pColorAttachmentFormats = &swapchain->m_Format,
.depthAttachmentFormat = vk::Format::eD24UnormS8Uint,
};
vk::GraphicsPipelineCreateInfo pipelineCreateInfo = {
.pNext = &renderingCreateInfo,
.stageCount = Cast<u32>(shaderStages.size()),
.pStages = shaderStages.data(),
.pVertexInputState = &vertexInputStateCreateInfo,
.pInputAssemblyState = &inputAssemblyStateCreateInfo,
.pViewportState = &viewportStateCreateInfo,
.pRasterizationState = &rasterizationStateCreateInfo,
.pMultisampleState = &multisampleStateCreateInfo,
.pDepthStencilState = &depthStencilStateCreateInfo,
.pColorBlendState = &colorBlendStateCreateInfo,
.pDynamicState = &dynamicStateCreateInfo,
.layout = pipelineLayout,
};
vk::Pipeline pipeline;
AbortIfFailed(device->m_Device.createGraphicsPipelines(nullptr, 1, &pipelineCreateInfo, nullptr, &pipeline));
device->SetName(pipeline, "Box Pipeline");
device->m_Device.destroy(vertexShaderModule, nullptr);
device->m_Device.destroy(fragmentShaderModule, nullptr);
return {device, pipelineLayout, pipeline, {descriptorSetLayout}};
}
vk::ShaderModule
CreateShader(const Device *device, cstr shaderFile)
{
eastl::vector<u32> shaderCode = ReadFile(shaderFile);
const vk::ShaderModuleCreateInfo shaderModuleCreateInfo = {
.codeSize = shaderCode.size() * sizeof(u32),
.pCode = shaderCode.data(),
};
vk::ShaderModule shaderModule;
AbortIfFailedMV(device->m_Device.createShaderModule(&shaderModuleCreateInfo, nullptr, &shaderModule),
"Shader {} could not be created.", shaderFile);
return shaderModule;
} }

View File

@ -0,0 +1,23 @@
[vk::binding(0, 0)] __DynamicResource<__DynamicResourceKind.General> gBuffers[];
[vk::binding(1, 0)] __DynamicResource<__DynamicResourceKind.Sampler> gSamplers[];
[vk::binding(2, 0)] __DynamicResource<__DynamicResourceKind.General> gStorageTextures[];
export T getDescriptorFromHandle<T>(DescriptorHandle<T> handle) where T : IOpaqueDescriptor
{
__target_switch
{
case spirv:
switch (T.kind) {
case DescriptorKind.Buffer:
return gBuffers[((uint2)handle).x].asOpaqueDescriptor<T>();
case DescriptorKind.CombinedTextureSampler:
return gSamplers[((uint2)handle).x].asOpaqueDescriptor<T>();
case DescriptorKind.Texture:
return gStorageTextures[((uint2)handle).x].asOpaqueDescriptor<T>();
default:
return defaultGetDescriptorFromHandle(handle);
}
default:
return defaultGetDescriptorFromHandle(handle);
}
}

View File

@ -1,11 +0,0 @@
#version 450
#pragma shader_stage(fragment)
layout (location = 0) in vec2 inUV;
layout (location = 0) out vec4 outColor;
layout(binding = 1) uniform sampler2D tex;
void main() {
outColor = vec4(texture(tex, inUV).rgb, 1.0f);
}

View File

@ -1,19 +0,0 @@
struct FS_Input {
float2 UV0 : TEXCOORD0;
};
struct FS_Output
{
float4 ColorTarget : SV_Target0;
};
[[vk::binding(1, 0)]] Texture2D<float4> Texture;
[[vk::binding(1, 0)]] SamplerState Sampler;
FS_Output main(FS_Input StageInput) {
FS_Output output;
output.ColorTarget = float4(Texture.Sample(Sampler, StageInput.UV0).rgb, 1.0);
return output;
}

View File

@ -0,0 +1,57 @@
import bindless;
struct VertexData
{
float4 position;
float2 texCoord0;
float2 _pad0;
};
struct CameraData
{
float4x4 model;
float4x4 view;
float4x4 projection;
};
struct PCB {
VertexData* vertexBuffer;
CameraData* cameraBuffer;
Sampler2D.Handle texture;
};
[vk::push_constant]
uniform PCB pcb;
struct VSIn {
uint vertexIndex : SV_VertexID;
};
struct VSOut
{
float4 position : SV_POSITION;
float2 texCoord0 : TEXCOORD0;
};
struct FSOut {
float4 Color;
};
[shader("vertex")]
func vsmain(VSIn input) -> VSOut {
VSOut output;
VertexData vd = pcb.vertexBuffer[input.vertexIndex];
output.position = mul(mul(mul(float4(vd.position.xyz, 1.0f), pcb.cameraBuffer->model), pcb.cameraBuffer->view), pcb.cameraBuffer->projection);
output.texCoord0 = vd.texCoord0;
return output;
}
[shader("fragment")]
func fsmain(VSOut input) -> FSOut {
FSOut outp;
outp.Color = float4(pcb.texture.Sample(input.texCoord0).rgb, 1.0);
return outp;
}

View File

@ -1,19 +0,0 @@
#version 450
#pragma shader_stage(vertex)
layout(location=0) in vec4 position;
layout(location=1) in vec2 uv0;
layout(location=0) out vec2 outUV;
layout(binding=0) uniform Camera {
mat4 model;
mat4 view;
mat4 proj;
} ubo;
void main() {
outUV = uv0;
gl_Position = ubo.proj * ubo.view * ubo.model * vec4(position.xyz, 1.0f);
// outColor = vec3(0.5f, 0.3f, 0.1f);
}

View File

@ -1,36 +0,0 @@
struct VS_Input
{
uint VertexIndex : SV_VertexID;
};
struct VS_Output
{
float2 UV0 : TEXCOORD0;
float4 VertexPosition : SV_Position;
};
struct CameraData {
float4x4 Model;
float4x4 View;
float4x4 Projection;
};
struct VertexData {
float4 Position;
float2 UV0;
};
[[vk::binding(0, 0)]] ConstantBuffer<CameraData> Camera;
[[vk::binding(2, 0)]] StructuredBuffer<VertexData> Vertices;
VS_Output main(VS_Input StageInput) {
VS_Output output;
output.UV0 = Vertices[StageInput.VertexIndex].UV0;
float4 position = Vertices[StageInput.VertexIndex].Position;
output.VertexPosition = mul(Camera.Projection, mul(Camera.View, mul(Camera.Model, position)));
return output;
}

View File

@ -5,28 +5,25 @@ cmake_minimum_required(VERSION 3.13)
#set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fsanitize=undefined -fsanitize=address") #set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fsanitize=undefined -fsanitize=address")
find_path(TINYGLTF_INCLUDE_DIRS "tiny_gltf.h") find_path(TINYGLTF_INCLUDE_DIRS "tiny_gltf.h")
add_executable(model_render "model_render.cpp" add_executable(model_render
"pipeline_utils.cpp" "model_render.cpp"
"pipeline_utils.h" "asset_loader.cpp"
"asset_loader.cpp" "asset_loader.h"
"asset_loader.h" "light_manager.cpp"
"light_manager.cpp" "light_manager.h"
"light_manager.h" "nodes.cpp"
"gpu_resource_manager.cpp" "nodes.h"
"gpu_resource_manager.h" "ibl_helpers.cpp"
"nodes.cpp" "ibl_helpers.h"
"nodes.h" "tiny_gltf_setup.cpp")
"ibl_helpers.cpp"
"ibl_helpers.h")
add_shader(model_render "shader/model.vs.hlsl") add_shader(model_render "shader/background.slang")
add_shader(model_render "shader/model.ps.hlsl") add_shader(model_render "shader/bindless.slang")
add_shader(model_render "shader/eqrect_to_cube.cs.hlsl") add_shader(model_render "shader/common_structs.slang")
add_shader(model_render "shader/background.vs.hlsl") add_shader(model_render "shader/environment.slang")
add_shader(model_render "shader/background.ps.hlsl") add_shader(model_render "shader/eqrect_to_cube.slang")
add_shader(model_render "shader/diffuse_irradiance.cs.hlsl") add_shader(model_render "shader/ibl_common.slang")
add_shader(model_render "shader/prefilter.cs.hlsl") add_shader(model_render "shader/model.slang")
add_shader(model_render "shader/brdf_lut.cs.hlsl")
target_link_libraries(model_render PRIVATE aster_core) target_link_libraries(model_render PRIVATE aster_core)
target_link_libraries(model_render PRIVATE util_helper) target_link_libraries(model_render PRIVATE util_helper)

View File

@ -1,49 +1,59 @@
// ============================================= // =============================================
// Aster: asset_loader.cpp // Aster: asset_loader.cpp
// Copyright (c) 2020-2024 Anish Bhobe // Copyright (c) 2020-2025 Anish Bhobe
// ============================================= // =============================================
#define TINYGLTF_NOEXCEPTION
#define JSON_NOEXCEPTION
#define TINYGLTF_IMPLEMENTATION
#define STB_IMAGE_IMPLEMENTATION
#define STB_IMAGE_WRITE_IMPLEMENTATION
#include "aster/core/buffer.h" #include "aster/core/buffer.h"
#include "aster/core/device.h" #include "aster/core/device.h"
#include "aster/core/image.h" #include "aster/core/image.h"
#include "gpu_resource_manager.h"
#include "helpers.h"
#include "asset_loader.h" #include "asset_loader.h"
#include "aster/systems/commit_manager.h"
#include "aster/systems/rendering_device.h"
#include <EASTL/fixed_vector.h> #include <EASTL/fixed_vector.h>
#include <EASTL/hash_map.h> #include <EASTL/hash_map.h>
#include <glm/gtc/type_ptr.hpp> #include <glm/gtc/type_ptr.hpp>
#include <filesystem>
#include <stb_image.h>
#include <tiny_gltf.h> #include <tiny_gltf.h>
#if defined(LoadImage) #if defined(LoadImage)
#undef LoadImage #undef LoadImage
#endif #endif
using namespace aster;
constexpr vk::CommandBufferBeginInfo OneTimeCmdBeginInfo = {.flags = vk::CommandBufferUsageFlagBits::eOneTimeSubmit}; constexpr vk::CommandBufferBeginInfo OneTimeCmdBeginInfo = {.flags = vk::CommandBufferUsageFlagBits::eOneTimeSubmit};
vec4 vec4
VectorToVec4(const std::vector<double> &vec) VectorToVec4(std::vector<double> const &vec)
{ {
if (vec.empty()) if (vec.empty())
{ {
return vec4{0.0f}; return vec4{0.0f};
} }
assert(vec.size() == 4); assert(vec.size() == 4);
return {vec[0], vec[1], vec[2], vec[3]}; return {vec[0], vec[1], vec[2], vec[3]};
} }
vec4
VectorToVec4(std::vector<double> const &vec, float w)
{
if (vec.empty())
{
return vec4{0.0f};
}
assert(vec.size() == 3);
return {vec[0], vec[1], vec[2], w};
}
vec3 vec3
VectorToVec3(const std::vector<double> &vec) VectorToVec3(std::vector<double> const &vec)
{ {
if (vec.empty()) if (vec.empty())
{ {
@ -54,51 +64,28 @@ VectorToVec3(const std::vector<double> &vec)
return {vec[0], vec[1], vec[2]}; return {vec[0], vec[1], vec[2]};
} }
void Ref<TextureView>
AssetLoader::LoadHdrImage(Texture *texture, cstr path, cstr name) const AssetLoader::LoadHdrImage(cstr path, cstr name) const
{ {
const Device *pDevice = m_ResourceManager->m_Device;
ERROR_IF(texture->IsValid(), "Expected invalid image.") THEN_ABORT(-1);
i32 x, y, nChannels; i32 x, y, nChannels;
f32 *data = stbi_loadf(path, &x, &y, &nChannels, 4); f32 *data = stbi_loadf(path, &x, &y, &nChannels, 4);
assert(nChannels == 3); assert(nChannels == 3);
ERROR_IF(!data, "Could not load {}", path) THEN_ABORT(-1); ERROR_IF(!data, "Could not load {}", path) THEN_ABORT(-1);
u32 width = Cast<u32>(x); u32 width = static_cast<u32>(x);
u32 height = Cast<u32>(y); u32 height = static_cast<u32>(y);
StagingBuffer stagingBuffer; auto texture = m_Device->CreateTexture2DWithView({
texture->Init(m_ResourceManager->m_Device, {width, height}, vk::Format::eR32G32B32A32Sfloat, false, path); .m_Format = vk::Format::eR32G32B32A32Sfloat,
assert(texture->IsValid()); .m_Extent = {width, height},
stagingBuffer.Init(m_ResourceManager->m_Device, (sizeof *data) * x * y * 4, "HDR Staging Buffer"); .m_Name = path,
stagingBuffer.Write(m_ResourceManager->m_Device, 0, stagingBuffer.GetSize(), data); .m_IsSampled = true,
.m_IsMipMapped = false,
stbi_image_free(data); .m_IsStorage = false,
});
#pragma region Setup Copy/Sync primitives #pragma region Setup Copy/Sync primitives
vk::BufferImageCopy2 copyRegion = {
.bufferOffset = 0,
.bufferRowLength = width,
.bufferImageHeight = height,
.imageSubresource =
{
.aspectMask = vk::ImageAspectFlagBits::eColor,
.mipLevel = 0,
.baseArrayLayer = 0,
.layerCount = 1,
},
.imageOffset = {0, 0, 0},
.imageExtent = texture->m_Extent,
};
vk::CopyBufferToImageInfo2 stagingInfo = {
.srcBuffer = stagingBuffer.m_Buffer,
.dstImage = texture->m_Image,
.dstImageLayout = vk::ImageLayout::eTransferDstOptimal,
.regionCount = 1,
.pRegions = &copyRegion,
};
vk::ImageMemoryBarrier2 readyToStageBarrier = { vk::ImageMemoryBarrier2 readyToStageBarrier = {
.srcStageMask = vk::PipelineStageFlagBits2::eAllCommands, .srcStageMask = vk::PipelineStageFlagBits2::eAllCommands,
.srcAccessMask = vk::AccessFlagBits2::eNone, .srcAccessMask = vk::AccessFlagBits2::eNone,
@ -108,7 +95,7 @@ AssetLoader::LoadHdrImage(Texture *texture, cstr path, cstr name) const
.newLayout = vk::ImageLayout::eTransferDstOptimal, .newLayout = vk::ImageLayout::eTransferDstOptimal,
.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
.image = texture->m_Image, .image = texture->GetImage(),
.subresourceRange = .subresourceRange =
{ {
.aspectMask = vk::ImageAspectFlagBits::eColor, .aspectMask = vk::ImageAspectFlagBits::eColor,
@ -131,9 +118,9 @@ AssetLoader::LoadHdrImage(Texture *texture, cstr path, cstr name) const
.dstAccessMask = vk::AccessFlagBits2::eShaderRead, .dstAccessMask = vk::AccessFlagBits2::eShaderRead,
.oldLayout = vk::ImageLayout::eTransferDstOptimal, .oldLayout = vk::ImageLayout::eTransferDstOptimal,
.newLayout = vk::ImageLayout::eShaderReadOnlyOptimal, .newLayout = vk::ImageLayout::eShaderReadOnlyOptimal,
.srcQueueFamilyIndex = m_TransferQueueIndex, .srcQueueFamilyIndex = m_Device->m_TransferQueueFamily,
.dstQueueFamilyIndex = m_GraphicsQueueIndex, .dstQueueFamilyIndex = m_Device->m_PrimaryQueueFamily,
.image = texture->m_Image, .image = texture->GetImage(),
.subresourceRange = .subresourceRange =
{ {
.aspectMask = vk::ImageAspectFlagBits::eColor, .aspectMask = vk::ImageAspectFlagBits::eColor,
@ -151,49 +138,31 @@ AssetLoader::LoadHdrImage(Texture *texture, cstr path, cstr name) const
}; };
#pragma endregion #pragma endregion
AbortIfFailed(m_CommandBuffer.begin(&OneTimeCmdBeginInfo)); auto context = m_Device->CreateTransferContext();
context.Begin();
#if !defined(ASTER_NDEBUG) eastl::fixed_string<char, 128> loadActionName = "Load: ";
StackString<128> loadActionName = "Load: ";
loadActionName += name ? name : path; loadActionName += name ? name : path;
vk::DebugUtilsLabelEXT debugLabel = { context.BeginDebugRegion(loadActionName.c_str());
.pLabelName = loadActionName.c_str(),
.color = std::array{1.0f, 1.0f, 1.0f, 1.0f},
};
m_CommandBuffer.beginDebugUtilsLabelEXT(&debugLabel);
#endif
m_CommandBuffer.pipelineBarrier2(&readyToStageDependency); context.Dependency(readyToStageDependency);
m_CommandBuffer.copyBufferToImage2(&stagingInfo); context.UploadTexture(texture->m_Image, {reinterpret_cast<u8 *>(data), (sizeof *data) * x * y * 4});
m_CommandBuffer.pipelineBarrier2(&postStagingDependency); context.Dependency(postStagingDependency);
#if !defined(ASTER_NDEBUG) context.EndDebugRegion();
m_CommandBuffer.endDebugUtilsLabelEXT();
#endif
AbortIfFailed(m_CommandBuffer.end()); context.End();
vk::SubmitInfo submitInfo = { auto rcpt = m_Device->Submit(context);
.waitSemaphoreCount = 0, stbi_image_free(data);
.pWaitDstStageMask = nullptr,
.commandBufferCount = 1,
.pCommandBuffers = &m_CommandBuffer,
};
vk::Fence fence; m_Device->WaitOn(rcpt);
vk::FenceCreateInfo fenceCreateInfo = {};
AbortIfFailed(pDevice->m_Device.createFence(&fenceCreateInfo, nullptr, &fence));
AbortIfFailed(m_TransferQueue.submit(1, &submitInfo, fence));
AbortIfFailed(pDevice->m_Device.waitForFences(1, &fence, true, MaxValue<u32>));
pDevice->m_Device.destroy(fence, nullptr);
AbortIfFailed(pDevice->m_Device.resetCommandPool(m_CommandPool, {})); return texture;
stagingBuffer.Destroy(pDevice);
} }
void void
GenerateMipMaps(vk::CommandBuffer commandBuffer, Texture *texture, vk::ImageLayout initialLayout, GenerateMipMaps(TransferContext &context, Ref<Texture> const &texture, vk::ImageLayout initialLayout,
vk::ImageLayout finalLayout, vk::PipelineStageFlags2 prevStage, vk::PipelineStageFlags2 finalStage) vk::ImageLayout finalLayout, vk::PipelineStageFlags2 prevStage, vk::PipelineStageFlags2 finalStage)
{ {
#if !defined(ASTER_NDEBUG) #if !defined(ASTER_NDEBUG)
@ -201,7 +170,7 @@ GenerateMipMaps(vk::CommandBuffer commandBuffer, Texture *texture, vk::ImageLayo
.pLabelName = "Generate Mipmap", .pLabelName = "Generate Mipmap",
.color = std::array{0.9f, 0.9f, 0.9f, 1.0f}, .color = std::array{0.9f, 0.9f, 0.9f, 1.0f},
}; };
commandBuffer.beginDebugUtilsLabelEXT(&label); context.BeginDebugRegion("Generate MipMap", {0.9, 0.9, 0.9, 1.0});
#endif #endif
vk::ImageMemoryBarrier2 imageStartBarrier = { vk::ImageMemoryBarrier2 imageStartBarrier = {
@ -243,7 +212,7 @@ GenerateMipMaps(vk::CommandBuffer commandBuffer, Texture *texture, vk::ImageLayo
} }
vk::DependencyInfo imageStartDependency = { vk::DependencyInfo imageStartDependency = {
.imageMemoryBarrierCount = Cast<u32>(startBarriers.size()), .imageMemoryBarrierCount = static_cast<u32>(startBarriers.size()),
.pImageMemoryBarriers = startBarriers.data(), .pImageMemoryBarriers = startBarriers.data(),
}; };
@ -324,10 +293,10 @@ GenerateMipMaps(vk::CommandBuffer commandBuffer, Texture *texture, vk::ImageLayo
// Mip Mapping // Mip Mapping
commandBuffer.pipelineBarrier2(&imageStartDependency); context.Dependency(imageStartDependency);
i32 prevMipWidth = Cast<i32>(texture->m_Extent.width); i32 prevMipWidth = static_cast<i32>(texture->m_Extent.width);
i32 prevMipHeight = Cast<i32>(texture->m_Extent.height); i32 prevMipHeight = static_cast<i32>(texture->m_Extent.height);
u32 maxPrevMip = texture->GetMipLevels() - 1; u32 maxPrevMip = texture->GetMipLevels() - 1;
for (u32 prevMipLevel = 0; prevMipLevel < maxPrevMip; ++prevMipLevel) for (u32 prevMipLevel = 0; prevMipLevel < maxPrevMip; ++prevMipLevel)
@ -349,47 +318,50 @@ GenerateMipMaps(vk::CommandBuffer commandBuffer, Texture *texture, vk::ImageLayo
nextMipBarrier.subresourceRange.baseMipLevel = currentMipLevel; nextMipBarrier.subresourceRange.baseMipLevel = currentMipLevel;
commandBuffer.blitImage2(&mipBlitInfo); context.Blit(mipBlitInfo);
commandBuffer.pipelineBarrier2(&interMipDependency); context.Dependency(interMipDependency);
prevMipHeight = currentMipHeight; prevMipHeight = currentMipHeight;
prevMipWidth = currentMipWidth; prevMipWidth = currentMipWidth;
} }
commandBuffer.pipelineBarrier2(&imageReadyDependency); context.Dependency(imageReadyDependency);
#if !defined(ASTER_NDEBUG) #if !defined(ASTER_NDEBUG)
commandBuffer.endDebugUtilsLabelEXT(); context.EndDebugRegion();
#endif #endif
} }
TextureHandle ResId<TextureView>
AssetLoader::LoadImageToGpu(StagingBuffer *stagingBuffer, tinygltf::Image *image, bool isSrgb) const AssetLoader::LoadImageToGpu(TransferContext &context, tinygltf::Image *image, bool isSrgb, cstr name) const
{ {
// TODO(Something not loading properly).
assert(image->component == 4); assert(image->component == 4);
assert(image->height > 0 && image->width > 0); assert(image->height > 0 && image->width > 0);
u32 height = Cast<u32>(image->height); #if !defined(ASTER_NDEBUG)
u32 width = Cast<u32>(image->width); auto assignedName = name ? name : image->name.empty() ? image->uri.c_str() : image->name.c_str();
#else
auto assignedName = nullptr;
#endif
u32 height = static_cast<u32>(image->height);
u32 width = static_cast<u32>(image->width);
vk::Format imageFormat = isSrgb ? vk::Format::eR8G8B8A8Srgb : vk::Format::eR8G8B8A8Unorm; vk::Format imageFormat = isSrgb ? vk::Format::eR8G8B8A8Srgb : vk::Format::eR8G8B8A8Unorm;
Texture texture; auto texture = m_Device->CreateTexture2D<Texture>({
.m_Format = imageFormat,
.m_Extent = {width, height},
.m_Name = assignedName,
.m_IsSampled = true,
.m_IsMipMapped = true,
.m_IsStorage = false,
});
usize byteSize = image->image.size(); eastl::fixed_string<char,128> loadActionName = "Load: ";
texture.Init(m_ResourceManager->m_Device, {.width = width, .height = height}, imageFormat, true, loadActionName += assignedName;
image->name.data()); context.BeginDebugRegion(loadActionName.c_str());
stagingBuffer->Init(m_ResourceManager->m_Device, byteSize);
stagingBuffer->Write(m_ResourceManager->m_Device, 0, byteSize, image->image.data());
#if !defined(ASTER_NDEBUG)
StackString<128> loadActionName = "Load: ";
loadActionName += image->name.empty() ? "<texture>" : image->name.c_str();
vk::DebugUtilsLabelEXT debugLabel = {
.pLabelName = loadActionName.c_str(),
.color = std::array{1.0f, 1.0f, 1.0f, 1.0f},
};
m_CommandBuffer.beginDebugUtilsLabelEXT(&debugLabel);
#endif
#pragma region Barriers and Blits #pragma region Barriers and Blits
@ -402,7 +374,7 @@ AssetLoader::LoadImageToGpu(StagingBuffer *stagingBuffer, tinygltf::Image *image
.newLayout = vk::ImageLayout::eTransferDstOptimal, .newLayout = vk::ImageLayout::eTransferDstOptimal,
.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
.image = texture.m_Image, .image = texture->m_Image,
.subresourceRange = .subresourceRange =
{ {
.aspectMask = vk::ImageAspectFlagBits::eColor, .aspectMask = vk::ImageAspectFlagBits::eColor,
@ -421,12 +393,14 @@ AssetLoader::LoadImageToGpu(StagingBuffer *stagingBuffer, tinygltf::Image *image
vk::ImageMemoryBarrier2 postStagingBarrier = { vk::ImageMemoryBarrier2 postStagingBarrier = {
.srcStageMask = vk::PipelineStageFlagBits2::eAllTransfer, .srcStageMask = vk::PipelineStageFlagBits2::eAllTransfer,
.srcAccessMask = vk::AccessFlagBits2::eTransferWrite,
.dstStageMask = vk::PipelineStageFlagBits2::eAllTransfer, .dstStageMask = vk::PipelineStageFlagBits2::eAllTransfer,
.dstAccessMask = vk::AccessFlagBits2::eTransferRead,
.oldLayout = vk::ImageLayout::eTransferDstOptimal, .oldLayout = vk::ImageLayout::eTransferDstOptimal,
.newLayout = vk::ImageLayout::eTransferSrcOptimal, .newLayout = vk::ImageLayout::eTransferSrcOptimal,
.srcQueueFamilyIndex = m_TransferQueueIndex, .srcQueueFamilyIndex = m_Device->m_TransferQueueFamily,
.dstQueueFamilyIndex = m_GraphicsQueueIndex, .dstQueueFamilyIndex = m_Device->m_PrimaryQueueFamily,
.image = texture.m_Image, .image = texture->m_Image,
.subresourceRange = .subresourceRange =
{ {
.aspectMask = vk::ImageAspectFlagBits::eColor, .aspectMask = vk::ImageAspectFlagBits::eColor,
@ -436,49 +410,28 @@ AssetLoader::LoadImageToGpu(StagingBuffer *stagingBuffer, tinygltf::Image *image
.layerCount = 1, .layerCount = 1,
}, },
}; };
;
vk::DependencyInfo postStagingDependency = { vk::DependencyInfo postStagingDependency = {
.imageMemoryBarrierCount = 1, .imageMemoryBarrierCount = 1,
.pImageMemoryBarriers = &postStagingBarrier, .pImageMemoryBarriers = &postStagingBarrier,
}; };
vk::BufferImageCopy2 imageCopy = {
.bufferOffset = 0,
.bufferRowLength = Cast<u32>(image->width),
.bufferImageHeight = Cast<u32>(image->height),
.imageSubresource =
{
.aspectMask = vk::ImageAspectFlagBits::eColor,
.mipLevel = 0,
.baseArrayLayer = 0,
.layerCount = 1,
},
.imageOffset = {},
.imageExtent = texture.m_Extent,
};
vk::CopyBufferToImageInfo2 stagingCopyInfo = {
.srcBuffer = stagingBuffer->m_Buffer,
.dstImage = texture.m_Image,
.dstImageLayout = vk::ImageLayout::eTransferDstOptimal,
.regionCount = 1,
.pRegions = &imageCopy,
};
#pragma endregion #pragma endregion
m_CommandBuffer.pipelineBarrier2(&imageStartDependency); context.Dependency(imageStartDependency);
m_CommandBuffer.copyBufferToImage2(&stagingCopyInfo); context.UploadTexture(texture, {image->image.data(), image->image.size()});
m_CommandBuffer.pipelineBarrier2(&postStagingDependency); context.Dependency(postStagingDependency);
GenerateMipMaps(m_CommandBuffer, &texture, vk::ImageLayout::eTransferSrcOptimal, GenerateMipMaps(context, texture, vk::ImageLayout::eTransferSrcOptimal, vk::ImageLayout::eShaderReadOnlyOptimal);
vk::ImageLayout::eShaderReadOnlyOptimal);
#if !defined(ASTER_NDEBUG) #if !defined(ASTER_NDEBUG)
m_CommandBuffer.endDebugUtilsLabelEXT(); context.EndDebugRegion();
#endif #endif
return m_ResourceManager->CommitTexture(&texture); auto textureView = m_Device->CreateView<TextureView>(
{.m_Image = texture, .m_Name = image->name.data(), .m_AspectMask = vk::ImageAspectFlagBits::eColor});
return m_Device->m_CommitManager->CommitTexture(textureView);
} }
Model Model
@ -488,10 +441,8 @@ AssetLoader::LoadModelToGpu(cstr path, cstr name)
tinygltf::Model model; tinygltf::Model model;
tinygltf::TinyGLTF loader; tinygltf::TinyGLTF loader;
const Device *pDevice = m_ResourceManager->m_Device; auto const fsPath = fs::absolute(path);
auto const ext = fsPath.extension();
const auto fsPath = fs::absolute(path);
const auto ext = fsPath.extension();
if (ext == GLTF_ASCII_FILE_EXTENSION) if (ext == GLTF_ASCII_FILE_EXTENSION)
{ {
std::string err; std::string err;
@ -513,42 +464,36 @@ AssetLoader::LoadModelToGpu(cstr path, cstr name)
} }
} }
AbortIfFailed(m_CommandBuffer.begin(&OneTimeCmdBeginInfo)); auto context = m_Device->CreateTransferContext();
#if !defined(ASTER_NDEBUG) context.Begin();
StackString<128> loadActionName = "Load: ";
eastl::fixed_string<char,128> loadActionName = "Load: ";
loadActionName += name ? name : path; loadActionName += name ? name : path;
vk::DebugUtilsLabelEXT debugLabel = { context.BeginDebugRegion(loadActionName.c_str());
.pLabelName = loadActionName.c_str(),
.color = std::array{1.0f, 1.0f, 1.0f, 1.0f},
};
m_CommandBuffer.beginDebugUtilsLabelEXT(&debugLabel);
#endif
eastl::vector<StagingBuffer> stagingBuffers; eastl::hash_map<i32, ResId<TextureView>> textureHandleMap;
eastl::hash_map<i32, TextureHandle> textureHandleMap;
eastl::vector<Material> materials; eastl::vector<Material> materials;
StorageBuffer materialsBuffer; Ref<Buffer> materialsBuffer;
BufferHandle materialsHandle;
if (!model.materials.empty()) if (!model.materials.empty())
{ {
auto getTextureHandle = [this, &textureHandleMap, &stagingBuffers, &model](i32 index, // TODO("Something broken on load here.");
bool isSrgb) -> TextureHandle { auto getTextureHandle = [this, &context, &textureHandleMap,
&model](i32 index, bool const isSrgb) -> ResId<TextureView> {
if (index < 0) if (index < 0)
{ {
return {}; return NullId{};
} }
const auto iter = textureHandleMap.find(index); if (auto const iter = textureHandleMap.find(index); iter != textureHandleMap.end())
if (iter != textureHandleMap.end())
{ {
return iter->second; return iter->second;
} }
auto *image = &model.images[index]; auto const &texture = model.textures[index];
TextureHandle handle = LoadImageToGpu(&stagingBuffers.push_back(), image, isSrgb); auto *image = &model.images[texture.source];
auto handle = LoadImageToGpu(context, image, isSrgb, texture.name.empty() ? nullptr : texture.name.c_str());
textureHandleMap.emplace(index, handle); textureHandleMap.emplace(index, handle);
return handle; return handle;
}; };
@ -558,28 +503,22 @@ AssetLoader::LoadModelToGpu(cstr path, cstr name)
{ {
materials.push_back({ materials.push_back({
.m_AlbedoFactor = VectorToVec4(material.pbrMetallicRoughness.baseColorFactor), .m_AlbedoFactor = VectorToVec4(material.pbrMetallicRoughness.baseColorFactor),
.m_EmissionFactor = VectorToVec3(material.emissiveFactor), .m_EmissionFactor = VectorToVec4(material.emissiveFactor, 0.0f),
.m_MetalFactor = Cast<f32>(material.pbrMetallicRoughness.metallicFactor),
.m_RoughFactor = Cast<f32>(material.pbrMetallicRoughness.roughnessFactor),
.m_AlbedoTex = getTextureHandle(material.pbrMetallicRoughness.baseColorTexture.index, true), .m_AlbedoTex = getTextureHandle(material.pbrMetallicRoughness.baseColorTexture.index, true),
.m_NormalTex = getTextureHandle(material.normalTexture.index, false), .m_NormalTex = getTextureHandle(material.normalTexture.index, false),
.m_MetalRoughTex = .m_MetalRoughTex =
getTextureHandle(material.pbrMetallicRoughness.metallicRoughnessTexture.index, false), getTextureHandle(material.pbrMetallicRoughness.metallicRoughnessTexture.index, false),
.m_OcclusionTex = getTextureHandle(material.occlusionTexture.index, false), .m_OcclusionTex = getTextureHandle(material.occlusionTexture.index, false),
.m_EmissionTex = getTextureHandle(material.emissiveTexture.index, true), .m_EmissionTex = getTextureHandle(material.emissiveTexture.index, true),
.m_MetalFactor = static_cast<f32>(material.pbrMetallicRoughness.metallicFactor),
.m_RoughFactor = static_cast<f32>(material.pbrMetallicRoughness.roughnessFactor),
}); });
} }
usize materialsByteSize = materials.size() * sizeof materials[0]; usize materialsByteSize = materials.size() * sizeof materials[0];
materialsBuffer.Init(pDevice, materialsByteSize, false, name); materialsBuffer = m_Device->CreateStorageBuffer(materialsByteSize, name);
materialsHandle = m_ResourceManager->Commit(&materialsBuffer);
StagingBuffer &materialStaging = stagingBuffers.push_back(); context.UploadBuffer(materialsBuffer, materials);
materialStaging.Init(pDevice, materialsByteSize);
materialStaging.Write(pDevice, 0, materialsByteSize, materials.data());
vk::BufferCopy bufferCopy = {.srcOffset = 0, .dstOffset = 0, .size = materialsByteSize};
m_CommandBuffer.copyBuffer(materialStaging.m_Buffer, materialsBuffer.m_Buffer, 1, &bufferCopy);
} }
// TODO: Mesh reordering based on nodes AND OR meshoptimizer // TODO: Mesh reordering based on nodes AND OR meshoptimizer
@ -618,17 +557,17 @@ AssetLoader::LoadModelToGpu(cstr path, cstr name)
tinygltf::Buffer *posBuffer = &model.buffers[posBufferView->buffer]; tinygltf::Buffer *posBuffer = &model.buffers[posBufferView->buffer];
usize byteOffset = (posAccessor->byteOffset + posBufferView->byteOffset); usize byteOffset = (posAccessor->byteOffset + posBufferView->byteOffset);
vertexCount = Cast<u32>(posAccessor->count); vertexCount = static_cast<u32>(posAccessor->count);
vertexPositions.reserve(vertexOffset + vertexCount); vertexPositions.reserve(vertexOffset + vertexCount);
if (posAccessor->type == TINYGLTF_TYPE_VEC4) if (posAccessor->type == TINYGLTF_TYPE_VEC4)
{ {
vec4 *data = Recast<vec4 *>(posBuffer->data.data() + byteOffset); auto data = reinterpret_cast<vec4 *>(posBuffer->data.data() + byteOffset);
vertexPositions.insert(vertexPositions.end(), data, data + vertexCount); vertexPositions.insert(vertexPositions.end(), data, data + vertexCount);
} }
else if (posAccessor->type == TINYGLTF_TYPE_VEC3) else if (posAccessor->type == TINYGLTF_TYPE_VEC3)
{ {
vec3 *data = Recast<vec3 *>(posBuffer->data.data() + byteOffset); auto data = reinterpret_cast<vec3 *>(posBuffer->data.data() + byteOffset);
for (u32 i = 0; i < vertexCount; ++i) for (u32 i = 0; i < vertexCount; ++i)
{ {
vertexPositions.push_back(vec4(data[i], 1.0f)); vertexPositions.push_back(vec4(data[i], 1.0f));
@ -636,7 +575,7 @@ AssetLoader::LoadModelToGpu(cstr path, cstr name)
} }
else if (posAccessor->type == TINYGLTF_TYPE_VEC2) else if (posAccessor->type == TINYGLTF_TYPE_VEC2)
{ {
vec2 *data = Recast<vec2 *>(posBuffer->data.data() + byteOffset); auto data = reinterpret_cast<vec2 *>(posBuffer->data.data() + byteOffset);
for (u32 i = 0; i < vertexCount; ++i) for (u32 i = 0; i < vertexCount; ++i)
{ {
vertexPositions.push_back(vec4(data[i], 0.0f, 1.0f)); vertexPositions.push_back(vec4(data[i], 0.0f, 1.0f));
@ -660,7 +599,7 @@ AssetLoader::LoadModelToGpu(cstr path, cstr name)
if (normAccessor->type == TINYGLTF_TYPE_VEC4) if (normAccessor->type == TINYGLTF_TYPE_VEC4)
{ {
vec4 *data = Recast<vec4 *>(normBuffer->data.data() + byteOffset); auto data = reinterpret_cast<vec4 *>(normBuffer->data.data() + byteOffset);
vec4 *end = data + vertexCount; vec4 *end = data + vertexCount;
u32 idx = vertexOffset; u32 idx = vertexOffset;
@ -672,7 +611,7 @@ AssetLoader::LoadModelToGpu(cstr path, cstr name)
} }
else if (normAccessor->type == TINYGLTF_TYPE_VEC3) else if (normAccessor->type == TINYGLTF_TYPE_VEC3)
{ {
vec3 *data = Recast<vec3 *>(normBuffer->data.data() + byteOffset); auto data = reinterpret_cast<vec3 *>(normBuffer->data.data() + byteOffset);
for (u32 i = 0; i < vertexCount; ++i) for (u32 i = 0; i < vertexCount; ++i)
{ {
auto norm = vec4(data[i], 0.0f); auto norm = vec4(data[i], 0.0f);
@ -681,7 +620,7 @@ AssetLoader::LoadModelToGpu(cstr path, cstr name)
} }
else if (normAccessor->type == TINYGLTF_TYPE_VEC2) else if (normAccessor->type == TINYGLTF_TYPE_VEC2)
{ {
vec2 *data = Recast<vec2 *>(normBuffer->data.data() + byteOffset); auto data = reinterpret_cast<vec2 *>(normBuffer->data.data() + byteOffset);
for (u32 i = 0; i < vertexCount; ++i) for (u32 i = 0; i < vertexCount; ++i)
{ {
auto norm = vec4(data[i], 0.0f, 0.0f); auto norm = vec4(data[i], 0.0f, 0.0f);
@ -704,7 +643,7 @@ AssetLoader::LoadModelToGpu(cstr path, cstr name)
assert(uvAccessor->type == TINYGLTF_TYPE_VEC2 && assert(uvAccessor->type == TINYGLTF_TYPE_VEC2 &&
uvAccessor->componentType == TINYGLTF_COMPONENT_TYPE_FLOAT); uvAccessor->componentType == TINYGLTF_COMPONENT_TYPE_FLOAT);
{ {
vec2 *data = Recast<vec2 *>(uvBuffer->data.data() + byteOffset); auto data = reinterpret_cast<vec2 *>(uvBuffer->data.data() + byteOffset);
vec2 *end = data + vertexCount; vec2 *end = data + vertexCount;
u32 idx = vertexOffset; u32 idx = vertexOffset;
vec2 *it = data; vec2 *it = data;
@ -727,7 +666,7 @@ AssetLoader::LoadModelToGpu(cstr path, cstr name)
if (colorAccessor->type == TINYGLTF_TYPE_VEC4) if (colorAccessor->type == TINYGLTF_TYPE_VEC4)
{ {
vec4 *data = Recast<vec4 *>(colorBuffer->data.data() + byteOffset); auto data = reinterpret_cast<vec4 *>(colorBuffer->data.data() + byteOffset);
vec4 *end = data + vertexCount; vec4 *end = data + vertexCount;
u32 idx = vertexOffset; u32 idx = vertexOffset;
@ -739,7 +678,7 @@ AssetLoader::LoadModelToGpu(cstr path, cstr name)
} }
else if (colorAccessor->type == TINYGLTF_TYPE_VEC3) else if (colorAccessor->type == TINYGLTF_TYPE_VEC3)
{ {
vec3 *data = Recast<vec3 *>(colorBuffer->data.data() + byteOffset); auto data = reinterpret_cast<vec3 *>(colorBuffer->data.data() + byteOffset);
for (u32 i = 0; i < vertexCount; ++i) for (u32 i = 0; i < vertexCount; ++i)
{ {
auto color = vec4(data[i], 1.0f); auto color = vec4(data[i], 1.0f);
@ -760,22 +699,22 @@ AssetLoader::LoadModelToGpu(cstr path, cstr name)
tinygltf::Buffer *indexBuffer = &model.buffers[indexBufferView->buffer]; tinygltf::Buffer *indexBuffer = &model.buffers[indexBufferView->buffer];
usize byteOffset = (indexAccessor->byteOffset + indexBufferView->byteOffset); usize byteOffset = (indexAccessor->byteOffset + indexBufferView->byteOffset);
indexCount = Cast<u32>(indexAccessor->count); indexCount = static_cast<u32>(indexAccessor->count);
indices.reserve(indexOffset + indexCount); indices.reserve(indexOffset + indexCount);
if (indexAccessor->componentType == TINYGLTF_COMPONENT_TYPE_UNSIGNED_INT) if (indexAccessor->componentType == TINYGLTF_COMPONENT_TYPE_UNSIGNED_INT)
{ {
u32 *data = Recast<u32 *>(indexBuffer->data.data() + byteOffset); auto data = reinterpret_cast<u32 *>(indexBuffer->data.data() + byteOffset);
indices.insert(indices.end(), data, data + indexCount); indices.insert(indices.end(), data, data + indexCount);
} }
else if (indexAccessor->componentType == TINYGLTF_COMPONENT_TYPE_UNSIGNED_SHORT) else if (indexAccessor->componentType == TINYGLTF_COMPONENT_TYPE_UNSIGNED_SHORT)
{ {
u16 *data = Recast<u16 *>(indexBuffer->data.data() + byteOffset); auto data = reinterpret_cast<u16 *>(indexBuffer->data.data() + byteOffset);
indices.insert(indices.end(), data, data + indexCount); indices.insert(indices.end(), data, data + indexCount);
} }
else if (indexAccessor->componentType == TINYGLTF_COMPONENT_TYPE_UNSIGNED_BYTE) else if (indexAccessor->componentType == TINYGLTF_COMPONENT_TYPE_UNSIGNED_BYTE)
{ {
u8 *data = Recast<u8 *>(indexBuffer->data.data() + byteOffset); auto data = reinterpret_cast<u8 *>(indexBuffer->data.data() + byteOffset);
indices.insert(indices.end(), data, data + indexCount); indices.insert(indices.end(), data, data + indexCount);
} }
} }
@ -810,12 +749,12 @@ AssetLoader::LoadModelToGpu(cstr path, cstr name)
{ {
eastl::function<void(i32, i32)> processNode = [&processNode, &model, &nodes, &meshPrimRanges, eastl::function<void(i32, i32)> processNode = [&processNode, &model, &nodes, &meshPrimRanges,
&meshPrimitives](i32 idx, i32 parent) -> void { &meshPrimitives](i32 idx, i32 parent) -> void {
const auto *node = &model.nodes[idx]; auto const *node = &model.nodes[idx];
vec3 nodeTranslation = vec3{0.0f}; auto nodeTranslation = vec3{0.0f};
quat nodeRotation = quat{1.0f, 0.0f, 0.0f, 0.0f}; auto nodeRotation = quat{1.0f, 0.0f, 0.0f, 0.0f};
vec3 nodeScale = vec3{1.0f}; auto nodeScale = vec3{1.0f};
mat4 nodeMatrix = mat4{1.0f}; auto nodeMatrix = mat4{1.0f};
if (node->translation.size() == 3) if (node->translation.size() == 3)
{ {
nodeTranslation = glm::make_vec3(node->translation.data()); nodeTranslation = glm::make_vec3(node->translation.data());
@ -833,21 +772,21 @@ AssetLoader::LoadModelToGpu(cstr path, cstr name)
{ {
nodeMatrix = glm::make_mat4(node->matrix.data()); nodeMatrix = glm::make_mat4(node->matrix.data());
} }
const mat4 transform = translate(mat4(1.0f), nodeTranslation) * mat4_cast(nodeRotation) * mat4 const transform = translate(mat4(1.0f), nodeTranslation) * mat4_cast(nodeRotation) *
scale(mat4(1.0f), nodeScale) * nodeMatrix; scale(mat4(1.0f), nodeScale) * nodeMatrix;
const i32 nodeArrayIndex = Cast<i32>(nodes.Add(transform, parent)); i32 const nodeArrayIndex = static_cast<i32>(nodes.Add(transform, parent));
if (node->mesh >= 0) if (node->mesh >= 0)
{ {
auto [start, count] = meshPrimRanges[node->mesh]; auto [start, count] = meshPrimRanges[node->mesh];
const auto end = start + count; auto const end = start + count;
for (usize i = start; i != end; ++i) for (usize i = start; i != end; ++i)
{ {
meshPrimitives[i].m_TransformIdx = nodeArrayIndex; meshPrimitives[i].m_TransformIdx = nodeArrayIndex;
} }
} }
for (const i32 child : node->children) for (i32 const child : node->children)
{ {
processNode(child, nodeArrayIndex); processNode(child, nodeArrayIndex);
} }
@ -862,76 +801,46 @@ AssetLoader::LoadModelToGpu(cstr path, cstr name)
nodes.Update(); nodes.Update();
StorageBuffer nodeBuffer; auto nodeBuffer = m_Device->CreateStorageBuffer(nodes.GetGlobalTransformByteSize());
nodeBuffer.Init(pDevice, nodes.GetGlobalTransformByteSize(), true); nodeBuffer->Write(0, nodes.GetGlobalTransformByteSize(), nodes.GetGlobalTransformPtr());
nodeBuffer.Write(pDevice, 0, nodes.GetGlobalTransformByteSize(), nodes.GetGlobalTransformPtr());
BufferHandle nodeHandle = m_ResourceManager->Commit(&nodeBuffer);
#pragma region Staging / Transfer / Uploads #pragma region Staging / Transfer / Uploads
BufferHandle positionBufferHandle; ResId<Buffer> positionBufferHandle = ResId<Buffer>::Null();
BufferHandle vertexDataHandle; ResId<Buffer> vertexDataHandle = ResId<Buffer>::Null();
IndexBuffer indexBuffer; Ref<IndexBuffer> indexBuffer;
{ auto positionBuffer = m_Device->CreateStorageBuffer(vertexPositions.size() * sizeof vertexPositions[0]);
auto uploadBufferData = [cmd = this->m_CommandBuffer, &stagingBuffers, pDevice](const Buffer *buffer, context.UploadBuffer(positionBuffer, vertexPositions);
const void *data) {
vk::BufferCopy bufferCopy = {.srcOffset = 0, .dstOffset = 0, .size = buffer->GetSize()};
StagingBuffer &stagingBuffer = stagingBuffers.push_back();
stagingBuffer.Init(pDevice, bufferCopy.size);
stagingBuffer.Write(pDevice, 0, bufferCopy.size, data);
cmd.copyBuffer(stagingBuffer.m_Buffer, buffer->m_Buffer, 1, &bufferCopy);
};
StorageBuffer positionBuffer; auto vertexDataBuffer = m_Device->CreateStorageBuffer(vertexData.size() * sizeof vertexData[0]);
positionBuffer.Init(pDevice, vertexPositions.size() * sizeof vertexPositions[0], false); context.UploadBuffer(vertexDataBuffer, vertexData);
positionBufferHandle = m_ResourceManager->Commit(&positionBuffer);
uploadBufferData(&positionBuffer, vertexPositions.data());
StorageBuffer vertexDataBuffer; // TODO: Index buffer needs to be separated.
vertexDataBuffer.Init(pDevice, vertexData.size() * sizeof vertexData[0], false); indexBuffer = CastBuffer<IndexBuffer>(
vertexDataHandle = m_ResourceManager->Commit(&vertexDataBuffer); m_Device->CreateIndexBuffer(indices.size() * sizeof indices[0], "Index Buffer"));
uploadBufferData(&vertexDataBuffer, vertexData.data()); context.UploadBuffer(indexBuffer, indices);
indexBuffer.Init(pDevice, indices.size() * sizeof indices[0]);
uploadBufferData(&indexBuffer, indices.data());
}
#pragma endregion #pragma endregion
#if !defined(ASTER_NDEBUG) #if !defined(ASTER_NDEBUG)
m_CommandBuffer.endDebugUtilsLabelEXT(); context.EndDebugRegion();
#endif #endif
AbortIfFailed(m_CommandBuffer.end()); context.End();
vk::SubmitInfo submitInfo = { auto rcpt = m_Device->Submit(context);
.waitSemaphoreCount = 0, m_Device->WaitOn(rcpt);
.pWaitDstStageMask = nullptr,
.commandBufferCount = 1,
.pCommandBuffers = &m_CommandBuffer,
};
vk::Fence fence;
vk::FenceCreateInfo fenceCreateInfo = {};
AbortIfFailed(pDevice->m_Device.createFence(&fenceCreateInfo, nullptr, &fence));
AbortIfFailed(m_TransferQueue.submit(1, &submitInfo, fence));
AbortIfFailed(pDevice->m_Device.waitForFences(1, &fence, true, MaxValue<u32>));
pDevice->m_Device.destroy(fence, nullptr);
AbortIfFailed(pDevice->m_Device.resetCommandPool(m_CommandPool, {}));
for (auto &buffer : stagingBuffers)
{
buffer.Destroy(pDevice);
}
Model::ModelHandles handles = { Model::ModelHandles handles = {
.m_VertexPositionHandle = positionBufferHandle, .m_VertexPositionHandle = positionBuffer,
.m_VertexDataHandle = vertexDataHandle, .m_VertexDataHandle = vertexDataBuffer,
.m_MaterialsHandle = materialsHandle, .m_MaterialsHandle = materialsBuffer,
.m_NodeHandle = nodeHandle, .m_NodeHandle = nodeBuffer,
}; };
Model::ModelHandlesData handlesData = handles;
auto handlesBuffer = m_Device->CreateStorageBuffer(sizeof handlesData, "Materials");
handlesBuffer->Write(0, sizeof handlesData, &handlesData);
eastl::vector<TextureHandle> textureHandles; eastl::vector<ResId<TextureView>> textureHandles;
textureHandles.reserve(textureHandleMap.size()); textureHandles.reserve(textureHandleMap.size());
for (auto &[key, val] : textureHandleMap) for (auto &[key, val] : textureHandleMap)
@ -940,139 +849,45 @@ AssetLoader::LoadModelToGpu(cstr path, cstr name)
} }
return Model{ return Model{
m_ResourceManager, std::move(textureHandles), std::move(nodes), handles, indexBuffer, meshPrimitives, textureHandles, std::move(nodes), nodeBuffer, handles, handlesBuffer, indexBuffer, meshPrimitives,
}; };
} }
Model::Model(GpuResourceManager *resourceManager, eastl::vector<TextureHandle> &&textureHandles, Nodes &&nodes, Model::Model(eastl::vector<ResId<TextureView>> &textureHandles, Nodes &&nodes, Ref<Buffer> nodeBuffer,
const ModelHandles &handles, const IndexBuffer &indexBuffer, ModelHandles &handles, Ref<Buffer> modelHandlesBuffer, Ref<IndexBuffer> indexBuffer,
const eastl::vector<MeshPrimitive> &meshPrimitives) eastl::vector<MeshPrimitive> const &meshPrimitives)
: m_ResourceManager(resourceManager) : m_TextureHandles(std::move(textureHandles))
, m_TextureHandles(std::move(textureHandles))
, m_Nodes(std::move(nodes)) , m_Nodes(std::move(nodes))
, m_Handles(handles) , m_Handles(std::move(handles))
, m_IndexBuffer(indexBuffer) , m_NodeBuffer(std::move(nodeBuffer))
, m_IndexBuffer(std::move(indexBuffer))
, m_ModelHandlesBuffer(std::move(modelHandlesBuffer))
, m_MeshPrimitives(meshPrimitives) , m_MeshPrimitives(meshPrimitives)
{ {
} }
Model::Model(Model &&other) noexcept mat4 const &
: m_ResourceManager(Take(other.m_ResourceManager))
, m_TextureHandles(std::move(other.m_TextureHandles))
, m_Handles(other.m_Handles)
, m_IndexBuffer(other.m_IndexBuffer)
, m_MeshPrimitives(std::move(other.m_MeshPrimitives))
{
}
Model &
Model::operator=(Model &&other) noexcept
{
if (this == &other)
return *this;
m_ResourceManager = Take(other.m_ResourceManager);
m_TextureHandles = std::move(other.m_TextureHandles);
m_Handles = other.m_Handles;
m_IndexBuffer = other.m_IndexBuffer;
m_MeshPrimitives = std::move(other.m_MeshPrimitives);
return *this;
}
const mat4 &
Model::GetModelTransform() const Model::GetModelTransform() const
{ {
return m_Nodes[0]; return m_Nodes[0];
} }
void void
Model::SetModelTransform(const mat4 &transform) Model::SetModelTransform(mat4 const &transform)
{ {
m_Nodes.Set(0, transform); m_Nodes.Set(0, transform);
} }
Model::~Model()
{
if (!m_ResourceManager)
return;
m_IndexBuffer.Destroy(m_ResourceManager->m_Device);
m_ResourceManager->Release(m_Handles.m_VertexDataHandle);
m_ResourceManager->Release(m_Handles.m_NodeHandle);
m_ResourceManager->Release(m_Handles.m_VertexPositionHandle);
m_ResourceManager->Release(m_Handles.m_MaterialsHandle);
for (const TextureHandle &handle : m_TextureHandles)
{
m_ResourceManager->Release(handle);
}
}
void void
Model::Update() Model::Update()
{ {
if (m_Nodes.Update()) if (m_Nodes.Update())
{ {
m_ResourceManager->Write(m_Handles.m_NodeHandle, 0, m_Nodes.GetGlobalTransformByteSize(), m_NodeBuffer->Write(0, m_Nodes.GetGlobalTransformByteSize(), m_Nodes.GetGlobalTransformPtr());
m_Nodes.GetGlobalTransformPtr());
} }
} }
AssetLoader::AssetLoader(GpuResourceManager *resourceManager, vk::Queue transferQueue, u32 transferQueueIndex, AssetLoader::AssetLoader(RenderingDevice &device)
u32 graphicsQueueIndex) : m_Device{&device}
: m_ResourceManager(resourceManager)
, m_TransferQueue(transferQueue)
, m_TransferQueueIndex(transferQueueIndex)
, m_GraphicsQueueIndex(graphicsQueueIndex)
{ {
const Device *pDevice = resourceManager->m_Device;
const vk::CommandPoolCreateInfo poolCreateInfo = {
.flags = vk::CommandPoolCreateFlagBits::eTransient,
.queueFamilyIndex = transferQueueIndex,
};
AbortIfFailedM(pDevice->m_Device.createCommandPool(&poolCreateInfo, nullptr, &m_CommandPool),
"Transfer command pool creation failed.");
pDevice->SetName(m_CommandPool, "Asset Loader Command Pool");
const vk::CommandBufferAllocateInfo commandBufferAllocateInfo = {
.commandPool = m_CommandPool,
.level = vk::CommandBufferLevel::ePrimary,
.commandBufferCount = 1,
};
AbortIfFailed(pDevice->m_Device.allocateCommandBuffers(&commandBufferAllocateInfo, &m_CommandBuffer));
pDevice->SetName(m_CommandBuffer, "Asset Loader Command Buffer");
}
AssetLoader::~AssetLoader()
{
if (m_ResourceManager)
{
m_ResourceManager->m_Device->m_Device.destroy(m_CommandPool, nullptr);
}
}
AssetLoader::AssetLoader(AssetLoader &&other) noexcept
: m_ResourceManager(Take(other.m_ResourceManager))
, m_CommandPool(other.m_CommandPool)
, m_CommandBuffer(other.m_CommandBuffer)
, m_TransferQueue(other.m_TransferQueue)
, m_TransferQueueIndex(other.m_TransferQueueIndex)
, m_GraphicsQueueIndex(other.m_GraphicsQueueIndex)
{
}
AssetLoader &
AssetLoader::operator=(AssetLoader &&other) noexcept
{
if (this == &other)
return *this;
m_ResourceManager = Take(other.m_ResourceManager);
m_CommandPool = other.m_CommandPool;
m_CommandBuffer = other.m_CommandBuffer;
m_TransferQueue = other.m_TransferQueue;
m_TransferQueueIndex = other.m_TransferQueueIndex;
m_GraphicsQueueIndex = other.m_GraphicsQueueIndex;
return *this;
} }

View File

@ -1,6 +1,6 @@
// ============================================= // =============================================
// Aster: asset_loader.h // Aster: asset_loader.h
// Copyright (c) 2020-2024 Anish Bhobe // Copyright (c) 2020-2025 Anish Bhobe
// ============================================= // =============================================
#pragma once #pragma once
@ -9,18 +9,28 @@
#include "aster/core/buffer.h" #include "aster/core/buffer.h"
#include "gpu_resource_manager.h" #include "aster/systems/resource.h"
#include "nodes.h" #include "nodes.h"
#include "tiny_gltf.h"
namespace aster
{
class RenderingDevice;
class ResourceManager;
class SamplerManager;
class BufferManager;
class ImageManager;
class CommitManager;
class TransferContext;
struct Image;
struct Texture;
} // namespace systems
namespace tinygltf namespace tinygltf
{ {
struct Image; struct Image;
} }
struct Image;
struct TextureHandle;
struct Texture;
constexpr auto GLTF_ASCII_FILE_EXTENSION = ".gltf"; constexpr auto GLTF_ASCII_FILE_EXTENSION = ".gltf";
constexpr auto GLTF_BINARY_FILE_EXTENSION = ".glb"; constexpr auto GLTF_BINARY_FILE_EXTENSION = ".glb";
@ -35,15 +45,15 @@ struct MeshPrimitive
struct Material struct Material
{ {
vec4 m_AlbedoFactor; // 16 16 vec4 m_AlbedoFactor; // 16 16
vec3 m_EmissionFactor; // 12 28 vec4 m_EmissionFactor; // 16 32
f32 m_MetalFactor; // 04 32 aster::ResId<aster::TextureView> m_AlbedoTex; // 08 40
f32 m_RoughFactor; // 04 36 aster::ResId<aster::TextureView> m_NormalTex; // 08 48
TextureHandle m_AlbedoTex; // 04 40 aster::ResId<aster::TextureView> m_MetalRoughTex; // 08 56
TextureHandle m_NormalTex; // 04 44 aster::ResId<aster::TextureView> m_OcclusionTex; // 08 64
TextureHandle m_MetalRoughTex; // 04 48 aster::ResId<aster::TextureView> m_EmissionTex; // 08 72
TextureHandle m_OcclusionTex; // 04 52 f32 m_MetalFactor; // 04 76
TextureHandle m_EmissionTex; // 04 56 f32 m_RoughFactor; // 04 80
}; };
struct VertexData struct VertexData
@ -56,49 +66,61 @@ struct VertexData
struct Model struct Model
{ {
GpuResourceManager *m_ResourceManager; eastl::vector<aster::ResId<aster::TextureView>> m_TextureHandles;
eastl::vector<TextureHandle> m_TextureHandles;
Nodes m_Nodes; Nodes m_Nodes;
struct ModelHandlesData
{
uptr m_VertexPositionHandle;
uptr m_VertexDataHandle;
uptr m_MaterialsHandle;
uptr m_NodeHandle;
};
struct ModelHandles struct ModelHandles
{ {
BufferHandle m_VertexPositionHandle; aster::Ref<aster::Buffer> m_VertexPositionHandle;
BufferHandle m_VertexDataHandle; aster::Ref<aster::Buffer> m_VertexDataHandle;
BufferHandle m_MaterialsHandle; aster::Ref<aster::Buffer> m_MaterialsHandle;
BufferHandle m_NodeHandle; aster::Ref<aster::Buffer> m_NodeHandle;
operator ModelHandlesData() const
{
return {
.m_VertexPositionHandle = m_VertexPositionHandle->GetDeviceAddress(),
.m_VertexDataHandle = m_VertexDataHandle->GetDeviceAddress(),
.m_MaterialsHandle = m_MaterialsHandle->GetDeviceAddress(),
.m_NodeHandle = m_NodeHandle->GetDeviceAddress(),
};
}
} m_Handles; } m_Handles;
IndexBuffer m_IndexBuffer; aster::Ref<aster::Buffer> m_NodeBuffer;
aster::Ref<aster::IndexBuffer> m_IndexBuffer;
aster::Ref<aster::Buffer> m_ModelHandlesBuffer;
eastl::vector<MeshPrimitive> m_MeshPrimitives; eastl::vector<MeshPrimitive> m_MeshPrimitives;
[[nodiscard]] const mat4 &GetModelTransform() const; [[nodiscard]] mat4 const &GetModelTransform() const;
void SetModelTransform(const mat4 &transform); void SetModelTransform(mat4 const &transform);
void Update(); void Update();
Model(GpuResourceManager *resourceManager, eastl::vector<TextureHandle> &&textureHandles, Nodes &&nodes, Model(eastl::vector<aster::ResId<aster::TextureView>> &textureHandles, Nodes &&nodes, aster::Ref<aster::Buffer> nodeBuffer,
const ModelHandles &handles, const IndexBuffer &indexBuffer, ModelHandles &handles, aster::Ref<aster::Buffer> modelHandlesBuffer, aster::Ref<aster::IndexBuffer> indexBuffer,
const eastl::vector<MeshPrimitive> &meshPrimitives); eastl::vector<MeshPrimitive> const &meshPrimitives);
~Model(); ~Model() = default;
Model(Model &&other) noexcept; Model(Model &&other) noexcept = default;
Model &operator=(Model &&other) noexcept; Model &operator=(Model &&other) noexcept = default;
Model(const Model &) = delete; Model(Model const &) = delete;
const Model &operator=(const Model &) = delete; Model const &operator=(Model const &) = delete;
}; };
struct AssetLoader struct AssetLoader
{ {
GpuResourceManager *m_ResourceManager; aster::RenderingDevice *m_Device;
vk::CommandPool m_CommandPool;
vk::CommandBuffer m_CommandBuffer;
vk::Queue m_TransferQueue;
u32 m_TransferQueueIndex;
u32 m_GraphicsQueueIndex;
void LoadHdrImage(Texture *texture, cstr path, cstr name = nullptr) const; aster::Ref<aster::TextureView> LoadHdrImage(cstr path, cstr name = nullptr) const;
TextureHandle LoadImageToGpu(StagingBuffer *stagingBuffer, tinygltf::Image *image, bool isSrgb) const;
Model LoadModelToGpu(cstr path, cstr name = nullptr); Model LoadModelToGpu(cstr path, cstr name = nullptr);
constexpr static auto ANormal = "NORMAL"; constexpr static auto ANormal = "NORMAL";
@ -110,17 +132,32 @@ struct AssetLoader
constexpr static auto AJoints0 = "JOINTS_0"; constexpr static auto AJoints0 = "JOINTS_0";
constexpr static auto AWeights0 = "WEIGHTS_0"; constexpr static auto AWeights0 = "WEIGHTS_0";
AssetLoader(GpuResourceManager *resourceManager, vk::Queue transferQueue, u32 transferQueueIndex, explicit AssetLoader(aster::RenderingDevice &device);
u32 graphicsQueueIndex);
~AssetLoader();
AssetLoader(AssetLoader &&other) noexcept; private:
AssetLoader &operator=(AssetLoader &&other) noexcept; aster::ResId<aster::TextureView>
LoadImageToGpu(aster::TransferContext &context, tinygltf::Image *image, bool isSrgb, cstr name = nullptr) const;
DISALLOW_COPY_AND_ASSIGN(AssetLoader);
}; };
void GenerateMipMaps(vk::CommandBuffer commandBuffer, Texture *texture, vk::ImageLayout initialLayout, void
vk::ImageLayout finalLayout, GenerateMipMaps(aster::TransferContext &context, aster::Ref<aster::Texture> const &textureView, vk::ImageLayout initialLayout,
vk::PipelineStageFlags2 prevStage = vk::PipelineStageFlagBits2::eAllCommands, vk::ImageLayout finalLayout, vk::PipelineStageFlags2 prevStage, vk::PipelineStageFlags2 finalStage);
vk::PipelineStageFlags2 finalStage = vk::PipelineStageFlagBits2::eAllCommands);
void
GenerateMipMaps(aster::TransferContext &context, aster::concepts::ImageRefTo<aster::Texture> auto &texture,
vk::ImageLayout initialLayout, vk::ImageLayout finalLayout,
vk::PipelineStageFlags2 prevStage = vk::PipelineStageFlagBits2::eAllCommands,
vk::PipelineStageFlags2 finalStage = vk::PipelineStageFlagBits2::eAllCommands)
{
GenerateMipMaps(context, aster::CastImage<aster::Texture>(texture), initialLayout, finalLayout, prevStage, finalStage);
}
void
GenerateMipMaps(aster::TransferContext &context, aster::concepts::ViewRefTo<aster::Texture> auto &texture,
vk::ImageLayout initialLayout, vk::ImageLayout finalLayout,
vk::PipelineStageFlags2 prevStage = vk::PipelineStageFlagBits2::eAllCommands,
vk::PipelineStageFlags2 finalStage = vk::PipelineStageFlagBits2::eAllCommands)
{
GenerateMipMaps(context, aster::CastImage<aster::Texture>(texture->m_Image), initialLayout, finalLayout, prevStage,
finalStage);
}

View File

@ -1,688 +0,0 @@
// =============================================
// Aster: gpu_resource_manager.cpp
// Copyright (c) 2020-2024 Anish Bhobe
// =============================================
#include "gpu_resource_manager.h"
#include "helpers.h"
#include "aster/core/buffer.h"
#include "aster/core/device.h"
#include "aster/core/image.h"
#include <EASTL/array.h>
void
TextureManager::Init(const u32 maxCapacity)
{
m_MaxCapacity = maxCapacity;
m_FreeHead = GpuResourceHandle::INVALID_HANDLE;
}
TextureHandle
TextureManager::Commit(Texture *texture)
{
ERROR_IF(!texture || !texture->IsValid(), "Texture must be valid for committal")
THEN_ABORT(-1);
if (m_FreeHead != GpuResourceHandle::INVALID_HANDLE)
{
const u32 index = m_FreeHead;
Texture *allocatedTexture = &m_Textures[index];
assert(!allocatedTexture->IsValid());
m_FreeHead = *Recast<u32 *>(allocatedTexture);
// Ensure it is copyable.
static_assert(std::is_trivially_copyable_v<Texture>);
*allocatedTexture = *texture;
// Take ownership of the texture.
texture->m_Flags_ &= ~Texture::OWNED_BIT;
return {index};
}
const u32 index = Cast<u32>(m_Textures.size());
if (index < m_MaxCapacity)
{
Texture *allocatedTexture = &m_Textures.push_back();
// Ensure it is copyable.
static_assert(std::is_trivially_copyable_v<Texture>);
*allocatedTexture = *texture;
texture->m_Flags_ &= ~Texture::OWNED_BIT;
return {index};
}
ERROR("Out of Buffers") THEN_ABORT(-1);
}
Texture *
TextureManager::Fetch(const TextureHandle handle)
{
assert(!handle.IsInvalid());
return &m_Textures[handle.m_Index];
}
void
TextureManager::Release(const Device *device, const TextureHandle handle)
{
assert(!handle.IsInvalid());
Texture *allocatedTexture = &m_Textures[handle.m_Index];
allocatedTexture->Destroy(device);
assert(!allocatedTexture->IsValid());
*Recast<u32 *>(allocatedTexture) = m_FreeHead;
m_FreeHead = handle.m_Index;
}
void
TextureManager::Destroy(const Device *device)
{
for (auto &texture : m_Textures)
{
texture.Destroy(device);
}
}
void
BufferManager::Init(const u32 maxCapacity)
{
m_MaxCapacity = maxCapacity;
m_FreeHead = GpuResourceHandle::INVALID_HANDLE;
}
BufferHandle
BufferManager::Commit(StorageBuffer *buffer)
{
ERROR_IF(!buffer || !buffer->IsValid() || !buffer->IsOwned(), "Buffer must be valid and owned for commital")
THEN_ABORT(-1);
if (m_FreeHead != GpuResourceHandle::INVALID_HANDLE)
{
const u32 index = m_FreeHead;
StorageBuffer *allocatedBuffer = &m_Buffers[index];
assert(!allocatedBuffer->IsValid());
m_FreeHead = *Recast<u32 *>(allocatedBuffer);
// Ensure it is copyable.
static_assert(std::is_trivially_copyable_v<StorageBuffer>);
*allocatedBuffer = *buffer;
// Take ownership of the buffer.
buffer->m_Size_ &= ~StorageBuffer::OWNED_BIT;
return {index};
}
const u32 index = Cast<u32>(m_Buffers.size());
if (index < m_MaxCapacity)
{
StorageBuffer *allocatedBuffer = &m_Buffers.push_back();
// Ensure it is copyable.
static_assert(std::is_trivially_copyable_v<StorageBuffer>);
*allocatedBuffer = *buffer;
buffer->m_Size_ &= ~StorageBuffer::OWNED_BIT;
return {index};
}
ERROR("Out of Buffers") THEN_ABORT(-1);
}
StorageBuffer *
BufferManager::Fetch(const BufferHandle handle)
{
assert(!handle.IsInvalid());
return &m_Buffers[handle.m_Index];
}
void
BufferManager::Release(const Device *device, const BufferHandle handle)
{
assert(!handle.IsInvalid());
StorageBuffer *allocatedBuffer = &m_Buffers[handle.m_Index];
allocatedBuffer->Destroy(device);
assert(!allocatedBuffer->IsValid());
*Recast<u32 *>(allocatedBuffer) = m_FreeHead;
m_FreeHead = handle.m_Index;
}
void
BufferManager::Destroy(const Device *device)
{
for (auto &buffer : m_Buffers)
{
buffer.Destroy(device);
}
}
StorageTextureHandle
StorageTextureManager::Commit(StorageTexture *texture)
{
const TextureHandle tx = TextureManager::Commit(texture);
return {tx.m_Index};
}
StorageTexture *
StorageTextureManager::Fetch(const StorageTextureHandle handle)
{
assert(!handle.IsInvalid());
return Recast<StorageTexture *>(&m_Textures[handle.m_Index]);
}
void
StorageTextureManager::Release(const Device *device, const StorageTextureHandle handle)
{
TextureManager::Release(device, {handle.m_Index});
}
usize
HashSamplerCreateInfo(const vk::SamplerCreateInfo *createInfo)
{
usize hash = HashAny(createInfo->flags);
hash = HashCombine(hash, HashAny(createInfo->magFilter));
hash = HashCombine(hash, HashAny(createInfo->minFilter));
hash = HashCombine(hash, HashAny(createInfo->mipmapMode));
hash = HashCombine(hash, HashAny(createInfo->addressModeU));
hash = HashCombine(hash, HashAny(createInfo->addressModeV));
hash = HashCombine(hash, HashAny(createInfo->addressModeW));
hash = HashCombine(hash, HashAny(Cast<usize>(createInfo->mipLodBias * 1000))); // Resolution of 10^-3
hash = HashCombine(hash, HashAny(createInfo->anisotropyEnable));
hash = HashCombine(hash,
HashAny(Cast<usize>(createInfo->maxAnisotropy * 0x10))); // 16:1 Anisotropy is enough resolution
hash = HashCombine(hash, HashAny(createInfo->compareEnable));
hash = HashCombine(hash, HashAny(createInfo->compareOp));
hash = HashCombine(hash, HashAny(Cast<usize>(createInfo->minLod * 1000))); // 0.001 resolution is enough.
hash = HashCombine(hash,
HashAny(Cast<usize>(createInfo->maxLod * 1000))); // 0.001 resolution is enough. (1 == NO Clamp)
hash = HashCombine(hash, HashAny(createInfo->borderColor));
hash = HashCombine(hash, HashAny(createInfo->unnormalizedCoordinates));
return hash;
}
void
SamplerManager::Init(usize size)
{
m_Samplers.reserve(size);
m_SamplerHashes.reserve(size);
}
SamplerHandle
SamplerManager::Create(const Device *device, const vk::SamplerCreateInfo *createInfo)
{
const usize hash = HashSamplerCreateInfo(createInfo);
for (u32 index = 0; usize samplerHash : m_SamplerHashes)
{
if (samplerHash == hash)
{
return {index};
}
++index;
}
vk::Sampler sampler;
AbortIfFailed(device->m_Device.createSampler(createInfo, nullptr, &sampler));
const u32 index = Cast<u32>(m_SamplerHashes.size());
m_SamplerHashes.push_back(hash);
m_Samplers.push_back(sampler);
return {index};
}
vk::Sampler
SamplerManager::Fetch(const SamplerHandle handle)
{
assert(!handle.IsInvalid());
return m_Samplers[handle.m_Index];
}
void
SamplerManager::Destroy(const Device *device)
{
for (const auto &sampler : m_Samplers)
{
device->m_Device.destroy(sampler, nullptr);
}
m_Samplers.clear();
m_SamplerHashes.clear();
}
GpuResourceManager::WriteInfo::WriteInfo(vk::DescriptorBufferInfo info)
: uBufferInfo(info)
{
}
GpuResourceManager::WriteInfo::WriteInfo(vk::DescriptorImageInfo info)
: uImageInfo(info)
{
}
GpuResourceManager::WriteInfo::WriteInfo(vk::BufferView info)
: uBufferView(info)
{
}
BufferHandle
GpuResourceManager::Commit(StorageBuffer *storageBuffer)
{
const BufferHandle handle = m_BufferManager.Commit(storageBuffer);
m_WriteInfos.emplace_back(vk::DescriptorBufferInfo{
.buffer = storageBuffer->m_Buffer,
.offset = 0,
.range = storageBuffer->GetSize(),
});
m_Writes.push_back({
.dstSet = m_DescriptorSet,
.dstBinding = BUFFER_BINDING_INDEX,
.dstArrayElement = handle.m_Index,
.descriptorCount = 1,
.descriptorType = vk::DescriptorType::eStorageBuffer,
.pBufferInfo = &m_WriteInfos.back().uBufferInfo,
});
m_WriteOwner.emplace_back(HandleType::eBuffer, handle.m_Index);
#if !defined(ASTER_NDEBUG)
++m_CommitedBufferCount;
#endif
return handle;
}
void
GpuResourceManager::Write(const BufferHandle handle, const usize offset, const usize size, const void *data)
{
m_BufferManager.Fetch(handle)->Write(m_Device, offset, size, data);
}
void
GpuResourceManager::EraseWrites(u32 handleIndex, HandleType handleType)
{
auto writeIter = m_Writes.begin();
auto ownerIter = m_WriteOwner.begin();
const auto ownerEnd = m_WriteOwner.end();
while (ownerIter != ownerEnd)
{
if (ownerIter->first == handleType && ownerIter->second == handleIndex)
{
*writeIter = m_Writes.back();
*ownerIter = m_WriteOwner.back();
m_Writes.pop_back();
m_WriteOwner.pop_back();
return;
}
++ownerIter;
++writeIter;
}
}
void
GpuResourceManager::Release(BufferHandle handle)
{
if (handle.IsInvalid())
return;
EraseWrites(handle.m_Index, HandleType::eBuffer);
m_BufferManager.Release(m_Device, handle);
#if !defined(ASTER_NDEBUG)
--m_CommitedBufferCount;
#endif
}
void
GpuResourceManager::Release(StorageBuffer *storageBuffer, const BufferHandle handle)
{
assert(storageBuffer);
assert(!storageBuffer->IsValid());
StorageBuffer *internal = m_BufferManager.Fetch(handle);
*storageBuffer = *internal;
internal->m_Size_ &= ~StorageBuffer::OWNED_BIT;
Release(handle);
}
void
GpuResourceManager::Release(TextureHandle handle)
{
if (handle.IsInvalid())
return;
EraseWrites(handle.m_Index, HandleType::eTexture);
m_TextureManager.Release(m_Device, handle);
#if !defined(ASTER_NDEBUG)
--m_CommitedTextureCount;
#endif
}
void
GpuResourceManager::Release(Texture *texture, TextureHandle handle)
{
assert(texture);
assert(!texture->IsValid());
Texture *internal = m_TextureManager.Fetch(handle);
*texture = *internal;
internal->m_Flags_ &= ~Texture::OWNED_BIT;
Release(handle);
}
TextureHandle
GpuResourceManager::CommitTexture(Texture *texture, const SamplerHandle sampler)
{
TextureHandle handle = m_TextureManager.Commit(texture);
const vk::Sampler samplerImpl = sampler.IsInvalid() ? m_DefaultSampler : m_SamplerManager.Fetch(sampler);
m_WriteInfos.emplace_back(vk::DescriptorImageInfo{
.sampler = samplerImpl,
.imageView = texture->m_View,
.imageLayout = vk::ImageLayout::eShaderReadOnlyOptimal,
});
m_Writes.push_back({
.dstSet = m_DescriptorSet,
.dstBinding = TEXTURE_BINDING_INDEX,
.dstArrayElement = handle.m_Index,
.descriptorCount = 1,
.descriptorType = vk::DescriptorType::eCombinedImageSampler,
.pImageInfo = &m_WriteInfos.back().uImageInfo,
});
m_WriteOwner.emplace_back(HandleType::eTexture, handle.m_Index);
#if !defined(ASTER_NDEBUG)
++m_CommitedTextureCount;
#endif
return {handle};
}
StorageTextureHandle
GpuResourceManager::CommitStorageTexture(StorageTexture *storageTexture, SamplerHandle sampler)
{
StorageTextureHandle handle = m_StorageTextureManager.Commit(storageTexture);
vk::Sampler samplerImpl = sampler.IsInvalid() ? m_DefaultSampler : m_SamplerManager.Fetch(sampler);
m_WriteInfos.emplace_back(vk::DescriptorImageInfo{
.sampler = samplerImpl,
.imageView = storageTexture->m_View,
.imageLayout = vk::ImageLayout::eGeneral,
});
m_Writes.push_back({
.dstSet = m_DescriptorSet,
.dstBinding = STORAGE_TEXTURE_BINDING_INDEX,
.dstArrayElement = handle.m_Index,
.descriptorCount = 1,
.descriptorType = vk::DescriptorType::eStorageImage,
.pImageInfo = &m_WriteInfos.back().uImageInfo,
});
m_WriteOwner.emplace_back(HandleType::eStorageTexture, handle.m_Index);
#if !defined(ASTER_NDEBUG)
++m_CommitedStorageTextureCount;
#endif
return {handle};
}
void
GpuResourceManager::Release(StorageTextureHandle handle)
{
if (handle.IsInvalid())
return;
EraseWrites(handle.m_Index, HandleType::eTexture);
m_StorageTextureManager.Release(m_Device, handle);
#if !defined(ASTER_NDEBUG)
--m_CommitedStorageTextureCount;
#endif
}
void
GpuResourceManager::Release(StorageTexture *texture, const StorageTextureHandle handle)
{
assert(texture);
assert(!texture->IsValid());
StorageTexture *internal = m_StorageTextureManager.Fetch(handle);
*texture = *internal;
internal->m_Flags_ &= ~StorageTexture::OWNED_BIT;
Release(handle);
}
void
GpuResourceManager::Update()
{
if (m_Writes.empty() || m_WriteInfos.empty())
return;
m_Device->m_Device.updateDescriptorSets(Cast<u32>(m_Writes.size()), m_Writes.data(), 0, nullptr);
m_Writes.clear();
m_WriteInfos.clear();
m_WriteOwner.clear();
}
GpuResourceManager::GpuResourceManager(Device *device, u16 maxSize)
: m_Device(device)
{
vk::PhysicalDeviceProperties properties;
m_Device->m_PhysicalDevice.getProperties(&properties);
u32 buffersCount = eastl::min(properties.limits.maxPerStageDescriptorStorageBuffers - 1024, Cast<u32>(maxSize));
u32 texturesCount = eastl::min(properties.limits.maxPerStageDescriptorSampledImages - 1024, Cast<u32>(maxSize));
u32 storageTexturesCount =
eastl::min(properties.limits.maxPerStageDescriptorStorageImages - 1024, Cast<u32>(maxSize));
INFO("Max Buffer Count: {}", buffersCount);
INFO("Max Texture Count: {}", texturesCount);
INFO("Max Storage Texture Count: {}", storageTexturesCount);
m_BufferManager.Init(buffersCount);
m_TextureManager.Init(texturesCount);
m_StorageTextureManager.Init(storageTexturesCount);
m_SamplerManager.Init(storageTexturesCount);
m_DefaultSamplerCreateInfo = {
.magFilter = vk::Filter::eLinear,
.minFilter = vk::Filter::eLinear,
.mipmapMode = vk::SamplerMipmapMode::eLinear,
.addressModeU = vk::SamplerAddressMode::eRepeat,
.addressModeV = vk::SamplerAddressMode::eRepeat,
.addressModeW = vk::SamplerAddressMode::eRepeat,
.mipLodBias = 0.0f,
.anisotropyEnable = true,
.maxAnisotropy = properties.limits.maxSamplerAnisotropy,
.compareEnable = false,
.minLod = 0,
.maxLod = VK_LOD_CLAMP_NONE,
.borderColor = vk::BorderColor::eFloatOpaqueBlack,
.unnormalizedCoordinates = false,
};
m_DefaultSampler = m_SamplerManager.Fetch(m_SamplerManager.Create(device, &m_DefaultSamplerCreateInfo));
eastl::array poolSizes = {
vk::DescriptorPoolSize{
.type = vk::DescriptorType::eStorageBuffer,
.descriptorCount = buffersCount,
},
vk::DescriptorPoolSize{
.type = vk::DescriptorType::eCombinedImageSampler,
.descriptorCount = texturesCount,
},
vk::DescriptorPoolSize{
.type = vk::DescriptorType::eStorageImage,
.descriptorCount = storageTexturesCount,
},
};
const vk::DescriptorPoolCreateInfo poolCreateInfo = {
.flags = vk::DescriptorPoolCreateFlagBits::eUpdateAfterBind,
.maxSets = 1,
.poolSizeCount = Cast<u32>(poolSizes.size()),
.pPoolSizes = poolSizes.data(),
};
AbortIfFailed(device->m_Device.createDescriptorPool(&poolCreateInfo, nullptr, &m_DescriptorPool));
vk::DescriptorBindingFlags bindingFlags =
vk::DescriptorBindingFlagBits::ePartiallyBound | vk::DescriptorBindingFlagBits::eUpdateAfterBind;
eastl::array layoutBindingFlags = {
bindingFlags,
bindingFlags,
bindingFlags,
};
vk::DescriptorSetLayoutBindingFlagsCreateInfo bindingFlagsCreateInfo = {
.bindingCount = Cast<u32>(layoutBindingFlags.size()),
.pBindingFlags = layoutBindingFlags.data(),
};
eastl::array descriptorLayoutBindings = {
vk::DescriptorSetLayoutBinding{
.binding = BUFFER_BINDING_INDEX,
.descriptorType = vk::DescriptorType::eStorageBuffer,
.descriptorCount = Cast<u32>(buffersCount),
.stageFlags = vk::ShaderStageFlagBits::eAll,
},
vk::DescriptorSetLayoutBinding{
.binding = TEXTURE_BINDING_INDEX,
.descriptorType = vk::DescriptorType::eCombinedImageSampler,
.descriptorCount = Cast<u32>(texturesCount),
.stageFlags = vk::ShaderStageFlagBits::eAll,
},
vk::DescriptorSetLayoutBinding{
.binding = STORAGE_TEXTURE_BINDING_INDEX,
.descriptorType = vk::DescriptorType::eStorageImage,
.descriptorCount = Cast<u32>(storageTexturesCount),
.stageFlags = vk::ShaderStageFlagBits::eAll,
},
};
static_assert(layoutBindingFlags.size() == descriptorLayoutBindings.size());
const vk::DescriptorSetLayoutCreateInfo descriptorSetLayoutCreateInfo = {
.pNext = &bindingFlagsCreateInfo,
.flags = vk::DescriptorSetLayoutCreateFlagBits::eUpdateAfterBindPool,
.bindingCount = Cast<u32>(descriptorLayoutBindings.size()),
.pBindings = descriptorLayoutBindings.data(),
};
AbortIfFailed(device->m_Device.createDescriptorSetLayout(&descriptorSetLayoutCreateInfo, nullptr, &m_SetLayout));
// One descriptor is enough. Updating it at any time is safe. (Update until submit, data held when pending)
// https://registry.khronos.org/vulkan/specs/1.3-extensions/man/html/VK_EXT_descriptor_indexing.html
// https://github.com/KhronosGroup/Vulkan-Guide/blob/main/chapters/extensions/VK_EXT_descriptor_indexing.adoc
const vk::DescriptorSetAllocateInfo descriptorSetAllocateInfo = {
.descriptorPool = m_DescriptorPool,
.descriptorSetCount = 1,
.pSetLayouts = &m_SetLayout,
};
AbortIfFailed(device->m_Device.allocateDescriptorSets(&descriptorSetAllocateInfo, &m_DescriptorSet));
m_Device->SetName(m_SetLayout, "Bindless Layout");
m_Device->SetName(m_DescriptorPool, "Bindless Pool");
m_Device->SetName(m_DescriptorSet, "Bindless Set");
}
GpuResourceManager::~GpuResourceManager()
{
#if !defined(ASTER_NDEBUG)
WARN_IF(m_CommitedBufferCount > 0 || m_CommitedTextureCount > 0 || m_CommitedStorageTextureCount > 0,
"Resources alive: SSBO = {}, Textures = {}, RWTexture = {}", m_CommitedBufferCount, m_CommitedTextureCount,
m_CommitedStorageTextureCount);
#endif
m_BufferManager.Destroy(m_Device);
m_TextureManager.Destroy(m_Device);
m_StorageTextureManager.Destroy(m_Device);
m_SamplerManager.Destroy(m_Device);
m_Device->m_Device.destroy(m_DescriptorPool, nullptr);
m_Device->m_Device.destroy(m_SetLayout, nullptr);
}
GpuResourceManager::GpuResourceManager(GpuResourceManager &&other) noexcept
: m_WriteInfos(std::move(other.m_WriteInfos))
, m_Writes(std::move(other.m_Writes))
, m_WriteOwner(std::move(other.m_WriteOwner))
, m_BufferManager(std::move(other.m_BufferManager))
, m_TextureManager(std::move(other.m_TextureManager))
, m_StorageTextureManager(std::move(other.m_StorageTextureManager))
, m_SamplerManager(std::move(other.m_SamplerManager))
, m_Device(Take(other.m_Device))
, m_DescriptorPool(other.m_DescriptorPool)
, m_SetLayout(other.m_SetLayout)
, m_DescriptorSet(other.m_DescriptorSet)
#if !defined(ASTER_NDEBUG)
, m_CommitedBufferCount(other.m_CommitedBufferCount)
, m_CommitedTextureCount(other.m_CommitedTextureCount)
, m_CommitedStorageTextureCount(other.m_CommitedStorageTextureCount)
#endif
{
assert(!other.m_Device);
}
GpuResourceManager &
GpuResourceManager::operator=(GpuResourceManager &&other) noexcept
{
if (this == &other)
return *this;
m_WriteInfos = std::move(other.m_WriteInfos);
m_Writes = std::move(other.m_Writes);
m_WriteOwner = std::move(other.m_WriteOwner);
m_BufferManager = std::move(other.m_BufferManager);
m_TextureManager = std::move(other.m_TextureManager);
m_StorageTextureManager = std::move(other.m_StorageTextureManager);
m_SamplerManager = std::move(other.m_SamplerManager);
m_Device = Take(other.m_Device); // Ensure taken.
m_DescriptorPool = other.m_DescriptorPool;
m_SetLayout = other.m_SetLayout;
m_DescriptorSet = other.m_DescriptorSet;
#if !defined(ASTER_NDEBUG)
m_CommitedBufferCount = other.m_CommitedBufferCount;
m_CommitedTextureCount = other.m_CommitedTextureCount;
m_CommitedStorageTextureCount = other.m_CommitedStorageTextureCount;
#endif
assert(!other.m_Device);
return *this;
}
SamplerHandle
GpuResourceManager::CreateSampler(const vk::SamplerCreateInfo *samplerCreateInfo)
{
return m_SamplerManager.Create(m_Device, samplerCreateInfo);
}

View File

@ -1,175 +0,0 @@
// =============================================
// Aster: gpu_resource_manager.h
// Copyright (c) 2020-2024 Anish Bhobe
// =============================================
#pragma once
#include "aster/aster.h"
#include <EASTL/deque.h>
#include <EASTL/vector_map.h>
struct Device;
struct Texture;
struct StorageTexture;
struct StorageBuffer;
struct GpuResourceHandle
{
constexpr static u32 INVALID_HANDLE = MaxValue<u32>;
u32 m_Index = INVALID_HANDLE; // Default = invalid
[[nodiscard]] bool
IsInvalid() const
{
return m_Index == INVALID_HANDLE;
}
};
struct BufferHandle : GpuResourceHandle
{
};
struct TextureHandle : GpuResourceHandle
{
};
struct StorageTextureHandle : GpuResourceHandle
{
};
struct SamplerHandle : GpuResourceHandle
{
};
struct TextureManager
{
eastl::vector<Texture> m_Textures;
u32 m_MaxCapacity;
u32 m_FreeHead;
void Init(u32 maxCapacity);
TextureHandle Commit(Texture *texture);
Texture *Fetch(TextureHandle handle);
void Release(const Device *device, TextureHandle handle);
void Destroy(const Device *device);
};
struct BufferManager
{
eastl::vector<StorageBuffer> m_Buffers;
u32 m_MaxCapacity;
u32 m_FreeHead;
void Init(u32 maxCapacity);
BufferHandle Commit(StorageBuffer *buffer);
StorageBuffer *Fetch(BufferHandle handle);
void Release(const Device *device, BufferHandle handle);
void Destroy(const Device *device);
};
struct StorageTextureManager : TextureManager
{
StorageTextureHandle Commit(StorageTexture *texture);
StorageTexture *Fetch(StorageTextureHandle handle);
void Release(const Device *device, StorageTextureHandle handle);
};
struct SamplerManager
{
// There can only be so many samplers.
eastl::vector<vk::Sampler> m_Samplers;
eastl::vector<usize> m_SamplerHashes;
void Init(usize size);
SamplerHandle Create(const Device *device, const vk::SamplerCreateInfo *createInfo);
vk::Sampler Fetch(SamplerHandle handle);
void Destroy(const Device *device);
};
struct GpuResourceManager
{
private:
union WriteInfo {
vk::DescriptorBufferInfo uBufferInfo;
vk::DescriptorImageInfo uImageInfo;
vk::BufferView uBufferView;
WriteInfo()
{
}
explicit WriteInfo(vk::DescriptorBufferInfo info);
explicit WriteInfo(vk::DescriptorImageInfo info);
explicit WriteInfo(vk::BufferView info);
};
enum class HandleType
{
eBuffer,
eTexture,
eStorageTexture,
};
using WriteOwner = eastl::pair<HandleType, u32>;
eastl::deque<WriteInfo> m_WriteInfos;
eastl::vector<vk::WriteDescriptorSet> m_Writes;
eastl::vector<WriteOwner> m_WriteOwner;
vk::Sampler m_DefaultSampler;
BufferManager m_BufferManager;
TextureManager m_TextureManager;
StorageTextureManager m_StorageTextureManager;
SamplerManager m_SamplerManager;
void EraseWrites(u32 handleIndex, HandleType handleType);
public:
Device *m_Device;
constexpr static u32 BUFFER_BINDING_INDEX = 0;
constexpr static u32 TEXTURE_BINDING_INDEX = 1;
constexpr static u32 STORAGE_TEXTURE_BINDING_INDEX = 2;
vk::SamplerCreateInfo m_DefaultSamplerCreateInfo;
vk::DescriptorPool m_DescriptorPool;
vk::DescriptorSetLayout m_SetLayout;
vk::DescriptorSet m_DescriptorSet;
BufferHandle Commit(StorageBuffer *storageBuffer); // Commit to GPU and take Ownership
void Write(BufferHandle handle, usize offset, usize size, const void *data); // Write to buffer
void Release(BufferHandle handle); // Release and Destroy
void Release(StorageBuffer *storageBuffer, BufferHandle handle); // Release and Return
TextureHandle CommitTexture(Texture *texture, SamplerHandle sampler = {}); // Commit to GPU and take Ownership
void Release(TextureHandle handle); // Release and Destroy
void Release(Texture *texture, TextureHandle handle); // Release and Return
StorageTextureHandle
CommitStorageTexture(StorageTexture *storageTexture, SamplerHandle sampler = {}); // Commit to GPU and take Ownership
void Release(StorageTextureHandle handle); // Release and Destroy
void Release(StorageTexture *texture, StorageTextureHandle handle); // Release and Return
SamplerHandle CreateSampler(const vk::SamplerCreateInfo *samplerCreateInfo);
void Update(); // Update all the descriptors required.
// Ctor/Dtor
GpuResourceManager(Device *device, u16 maxSize);
~GpuResourceManager();
GpuResourceManager(GpuResourceManager &&other) noexcept;
GpuResourceManager &operator=(GpuResourceManager &&other) noexcept;
#if !defined(ASTER_NDEBUG)
usize m_CommitedBufferCount = 0;
usize m_CommitedTextureCount = 0;
usize m_CommitedStorageTextureCount = 0;
#endif
DISALLOW_COPY_AND_ASSIGN(GpuResourceManager);
};

View File

@ -1,6 +1,6 @@
// ============================================= // =============================================
// Aster: ibl_helpers.cpp // Aster: ibl_helpers.cpp
// Copyright (c) 2020-2024 Anish Bhobe // Copyright (c) 2020-2025 Anish Bhobe
// ============================================= // =============================================
#include "ibl_helpers.h" #include "ibl_helpers.h"
@ -9,82 +9,92 @@
#include "aster/core/image.h" #include "aster/core/image.h"
#include "asset_loader.h" #include "asset_loader.h"
#include "gpu_resource_manager.h"
#include "helpers.h" #include "aster/systems/commit_manager.h"
#include "pipeline_utils.h" #include "aster/systems/rendering_device.h"
#include <EASTL/fixed_vector.h> #include <EASTL/fixed_vector.h>
#include <EASTL/tuple.h> #include <EASTL/tuple.h>
constexpr cstr EQUIRECT_TO_CUBE_SHADER_FILE = "shader/eqrect_to_cube.cs.hlsl.spv"; constexpr auto EQUIRECT_TO_CUBE_SHADER_FILE = "eqrect_to_cube";
constexpr cstr DIFFUSE_IRRADIANCE_SHADER_FILE = "shader/diffuse_irradiance.cs.hlsl.spv"; constexpr auto ENVIRONMENT_SHADER_FILE = "environment";
constexpr cstr PREFILTER_SHADER_FILE = "shader/prefilter.cs.hlsl.spv"; constexpr auto DIFFUSE_IRRADIANCE_ENTRY = "diffuseIrradiance";
constexpr cstr BRDF_LUT_SHADER_FILE = "shader/brdf_lut.cs.hlsl.spv"; constexpr auto PREFILTER_ENTRY = "prefilter";
constexpr auto BRDF_LUT_ENTRY = "brdfLut";
void using namespace aster;
Environment::Destroy(GpuResourceManager *resourceManager)
{
resourceManager->Release(Take(m_Skybox));
resourceManager->Release(Take(m_Diffuse));
resourceManager->Release(Take(m_Prefilter));
resourceManager->Release(Take(m_BrdfLut));
}
Environment Environment
CreateCubeFromHdrEnv(AssetLoader *assetLoader, vk::Queue computeQueue, const u32 cubeSide, TextureHandle hdrEnv, CreateCubeFromHdrEnv(AssetLoader &assetLoader, u32 const cubeSide, ResId<TextureView> hdrEnv)
const cstr name)
{ {
GpuResourceManager *resMan = assetLoader->m_ResourceManager; RenderingDevice &device = *assetLoader.m_Device;
const Device *pDevice = resMan->m_Device; auto *commitManager = device.m_CommitManager.get();
vk::SamplerCreateInfo brdfLutSamplerCreateInfo = resMan->m_DefaultSamplerCreateInfo; auto skybox = device.CreateTextureCubeWithView<StorageTextureCubeView>({
brdfLutSamplerCreateInfo.addressModeU = vk::SamplerAddressMode::eClampToEdge; .m_Format = vk::Format::eR16G16B16A16Sfloat,
brdfLutSamplerCreateInfo.addressModeV = vk::SamplerAddressMode::eClampToEdge; .m_Side = cubeSide,
brdfLutSamplerCreateInfo.addressModeW = vk::SamplerAddressMode::eClampToEdge; .m_Name = "Skybox",
.m_IsSampled = true,
.m_IsMipMapped = true,
.m_IsStorage = true,
});
StorageTextureCube skybox; auto skyboxHandle = commitManager->CommitTexture(skybox);
StorageTextureCube diffuseIrradiance; auto skyboxStorageHandle = commitManager->CommitStorageImage(skybox);
StorageTextureCube prefilterCube;
StorageTexture brdfLut;
SamplerHandle brdfLutSampler;
skybox.Init(pDevice, cubeSide, vk::Format::eR16G16B16A16Sfloat, true, true, "Skybox"); auto diffuseIrradiance = device.CreateTextureCubeWithView<StorageTextureCubeView>({
TextureHandle skyboxHandle = resMan->CommitTexture(&skybox); .m_Format = vk::Format::eR16G16B16A16Sfloat,
StorageTextureHandle skyboxStorageHandle = resMan->CommitStorageTexture(&skybox); .m_Side = 64,
.m_Name = "Diffuse Irradiance",
.m_IsSampled = true,
.m_IsMipMapped = false,
.m_IsStorage = true,
});
auto diffuseIrradianceHandle = commitManager->CommitTexture(diffuseIrradiance);
auto diffuseIrradianceStorageHandle = commitManager->CommitStorageImage(diffuseIrradiance);
diffuseIrradiance.Init(pDevice, 64, vk::Format::eR16G16B16A16Sfloat, true, false, "Diffuse Irradiance"); auto prefilterCube = device.CreateTextureCubeWithView<StorageTextureCubeView>({
TextureHandle diffuseIrradianceHandle = resMan->CommitTexture(&diffuseIrradiance); .m_Format = vk::Format::eR16G16B16A16Sfloat,
StorageTextureHandle diffuseIrradianceStorageHandle = resMan->CommitStorageTexture(&diffuseIrradiance); .m_Side = cubeSide,
.m_Name = "Prefilter",
prefilterCube.Init(pDevice, cubeSide, vk::Format::eR16G16B16A16Sfloat, true, true, "Prefilter"); .m_IsSampled = true,
TextureHandle prefilterHandle = resMan->CommitTexture(&prefilterCube); // This stores the original view for us. .m_IsMipMapped = true,
.m_IsStorage = true,
});
auto prefilterHandle = commitManager->CommitTexture(prefilterCube); // This stores the original view for us.
constexpr u32 prefilterMipCountMax = 6; constexpr u32 prefilterMipCountMax = 6;
eastl::array<StorageTextureHandle, prefilterMipCountMax> prefilterStorageHandles; eastl::fixed_vector<ResId<StorageImageView>, prefilterMipCountMax> prefilterStorageHandles;
// All non-owning copies. // All non-owning copies.
for (u32 mipLevel = 0; auto &tex : prefilterStorageHandles) for (u8 mipLevel = 0; mipLevel < prefilterMipCountMax; ++mipLevel)
{ {
vk::ImageViewCreateInfo imageViewCreateInfo = { auto view = device.CreateView<StorageTextureCubeView>({
.image = prefilterCube.m_Image, .m_Image = CastImage<StorageTextureCube>(prefilterCube->m_Image),
.viewType = vk::ImageViewType::eCube, .m_ViewType = vk::ImageViewType::eCube,
.format = vk::Format::eR16G16B16A16Sfloat, .m_AspectMask = vk::ImageAspectFlagBits::eColor,
.components = vk::ComponentMapping{}, .m_MipLevelCount = 1,
.subresourceRange = .m_LayerCount = 6,
{ .m_BaseMipLevel = mipLevel,
.aspectMask = vk::ImageAspectFlagBits::eColor, .m_BaseLayer = 0,
.baseMipLevel = mipLevel++, });
.levelCount = 1, prefilterStorageHandles.push_back(commitManager->CommitStorageImage(view));
.baseArrayLayer = 0,
.layerCount = 6,
},
};
AbortIfFailed(pDevice->m_Device.createImageView(&imageViewCreateInfo, nullptr, &prefilterCube.m_View));
tex = resMan->CommitStorageTexture(&prefilterCube);
} }
brdfLut.Init(pDevice, {512, 512}, vk::Format::eR16G16Sfloat, true, "BRDF LUT"); auto brdfLut = device.CreateTexture2DWithView<StorageTextureView>({
brdfLutSampler = resMan->CreateSampler(&brdfLutSamplerCreateInfo); .m_Format = vk::Format::eR16G16Sfloat,
TextureHandle brdfLutHandle = resMan->CommitTexture(&brdfLut, brdfLutSampler); .m_Extent = {512, 512},
StorageTextureHandle brdfLutStorageHandle = resMan->CommitStorageTexture(&brdfLut); .m_Name = "BRDF LUT",
.m_IsSampled = true,
.m_IsMipMapped = false,
.m_IsStorage = true,
});
auto brdfLutSampler = device.CreateSampler({
.m_AddressModeU = vk::SamplerAddressMode::eClampToEdge,
.m_AddressModeV = vk::SamplerAddressMode::eClampToEdge,
.m_AddressModeW = vk::SamplerAddressMode::eClampToEdge,
});
auto brdfLutHandle = commitManager->CommitTexture(brdfLut, brdfLutSampler);
auto brdfLutStorageHandle = commitManager->CommitStorageImage(brdfLut);
#pragma region Dependencies and Copies #pragma region Dependencies and Copies
vk::ImageSubresourceRange cubeSubresRange = { vk::ImageSubresourceRange cubeSubresRange = {
@ -114,14 +124,14 @@ CreateCubeFromHdrEnv(AssetLoader *assetLoader, vk::Queue computeQueue, const u32
.subresourceRange = cubeSubresRange, .subresourceRange = cubeSubresRange,
}; };
eastl::fixed_vector<vk::ImageMemoryBarrier2, 4> readyToWriteBarriers(4, readyToWriteBarrierTemplate); eastl::fixed_vector<vk::ImageMemoryBarrier2, 4> readyToWriteBarriers(4, readyToWriteBarrierTemplate);
readyToWriteBarriers[0].image = skybox.m_Image; readyToWriteBarriers[0].image = skybox->GetImage();
readyToWriteBarriers[1].image = diffuseIrradiance.m_Image; readyToWriteBarriers[1].image = diffuseIrradiance->GetImage();
readyToWriteBarriers[2].image = prefilterCube.m_Image; readyToWriteBarriers[2].image = prefilterCube->GetImage();
readyToWriteBarriers[3].image = brdfLut.m_Image; readyToWriteBarriers[3].image = brdfLut->GetImage();
readyToWriteBarriers[3].subresourceRange = lutSubresRange; readyToWriteBarriers[3].subresourceRange = lutSubresRange;
vk::DependencyInfo readyToWriteDependency = { vk::DependencyInfo readyToWriteDependency = {
.imageMemoryBarrierCount = Cast<u32>(readyToWriteBarriers.size()), .imageMemoryBarrierCount = static_cast<u32>(readyToWriteBarriers.size()),
.pImageMemoryBarriers = readyToWriteBarriers.data(), .pImageMemoryBarriers = readyToWriteBarriers.data(),
}; };
@ -136,16 +146,16 @@ CreateCubeFromHdrEnv(AssetLoader *assetLoader, vk::Queue computeQueue, const u32
.subresourceRange = cubeSubresRange, .subresourceRange = cubeSubresRange,
}; };
auto skyboxToSampleBarrier = readyToSampleBarrierTemplate; auto skyboxToSampleBarrier = readyToSampleBarrierTemplate;
skyboxToSampleBarrier.image = skybox.m_Image; skyboxToSampleBarrier.image = skybox->GetImage();
auto diffIrrToSampleBarrier = readyToSampleBarrierTemplate; auto diffIrrToSampleBarrier = readyToSampleBarrierTemplate;
diffIrrToSampleBarrier.image = diffuseIrradiance.m_Image; diffIrrToSampleBarrier.image = diffuseIrradiance->GetImage();
auto prefilterToSampleBarrier = readyToSampleBarrierTemplate; auto prefilterToSampleBarrier = readyToSampleBarrierTemplate;
prefilterToSampleBarrier.image = prefilterCube.m_Image; prefilterToSampleBarrier.image = prefilterCube->GetImage();
auto brdfToSampleBarrier = readyToSampleBarrierTemplate; auto brdfToSampleBarrier = readyToSampleBarrierTemplate;
prefilterToSampleBarrier.image = brdfLut.m_Image; prefilterToSampleBarrier.image = brdfLut->GetImage();
prefilterToSampleBarrier.subresourceRange = lutSubresRange; prefilterToSampleBarrier.subresourceRange = lutSubresRange;
vk::DependencyInfo skyboxToSampleDependency = { vk::DependencyInfo skyboxToSampleDependency = {
@ -169,103 +179,144 @@ CreateCubeFromHdrEnv(AssetLoader *assetLoader, vk::Queue computeQueue, const u32
struct SkyboxPushConstants struct SkyboxPushConstants
{ {
TextureHandle m_HdrEnvHandle; ResId<TextureView> m_HdrEnvHandle;
StorageTextureHandle m_OutputTexture; ResId<StorageImageView> m_OutputTexture;
u32 m_CubeSide; u32 m_CubeSide;
}; };
struct DiffuseIrradiancePushConstants struct DiffuseIrradiancePushConstants
{ {
TextureHandle m_SkyboxHandle; ResId<TextureView> m_SkyboxHandle;
StorageTextureHandle m_OutputTexture; ResId<StorageImageView> m_OutputTexture;
u32 m_CubeSide; u32 m_CubeSide;
}; };
struct PrefilterPushConstants struct PrefilterPushConstants
{ {
TextureHandle m_SkyboxHandle; ResId<TextureView> m_SkyboxHandle;
StorageTextureHandle m_OutputTexture; ResId<StorageImageView> m_OutputTexture;
u32 m_CubeSide; u32 m_CubeSide;
f32 m_Roughness; f32 m_Roughness;
u32 m_EnvSide; u32 m_EnvSide;
}; };
struct BrdfLutPushConstants struct BrdfLutPushConstants
{ {
StorageTextureHandle m_OutputTexture; ResId<StorageImageView> m_OutputTexture;
}; };
#pragma region Pipeline Creation etc #pragma region Pipeline Creation etc
vk::PushConstantRange pcr = { // vk::PushConstantRange pcr = {
.stageFlags = vk::ShaderStageFlagBits::eCompute, // .stageFlags = vk::ShaderStageFlagBits::eCompute,
.offset = 0, // .offset = 0,
.size = Cast<u32>(eastl::max(eastl::max(sizeof(SkyboxPushConstants), sizeof(BrdfLutPushConstants)), // .size = static_cast<u32>(
eastl::max(sizeof(DiffuseIrradiancePushConstants), sizeof(PrefilterPushConstants)))), // eastl::max(eastl::max(sizeof(SkyboxPushConstants), sizeof(BrdfLutPushConstants)),
}; // eastl::max(sizeof(DiffuseIrradiancePushConstants), sizeof(PrefilterPushConstants)))),
// };
vk::PipelineLayout pipelineLayout; // vk::PipelineLayout pipelineLayout;
const vk::PipelineLayoutCreateInfo layoutCreateInfo = { // const vk::PipelineLayoutCreateInfo layoutCreateInfo = {
.setLayoutCount = 1, // .setLayoutCount = 1,
.pSetLayouts = &resMan->m_SetLayout, // .pSetLayouts = &commitManager->GetDescriptorSetLayout(),
.pushConstantRangeCount = 1, // .pushConstantRangeCount = 1,
.pPushConstantRanges = &pcr, // .pPushConstantRanges = &pcr,
}; // };
AbortIfFailed(pDevice->m_Device.createPipelineLayout(&layoutCreateInfo, nullptr, &pipelineLayout)); // AbortIfFailed(device.m_Device->createPipelineLayout(&layoutCreateInfo, nullptr, &pipelineLayout));
const auto eqRectToCubeShader = CreateShader(pDevice, EQUIRECT_TO_CUBE_SHADER_FILE); // const auto eqRectToCubeShader = CreateShader(pDevice, EQUIRECT_TO_CUBE_SHADER_FILE);
const auto diffuseRadianceShader = CreateShader(pDevice, DIFFUSE_IRRADIANCE_SHADER_FILE); // const auto diffuseRadianceShader = CreateShader(pDevice, DIFFUSE_IRRADIANCE_SHADER_FILE);
const auto prefilterShader = CreateShader(pDevice, PREFILTER_SHADER_FILE); // const auto prefilterShader = CreateShader(pDevice, PREFILTER_SHADER_FILE);
const auto brdfLutShader = CreateShader(pDevice, BRDF_LUT_SHADER_FILE); // const auto brdfLutShader = CreateShader(pDevice, BRDF_LUT_SHADER_FILE);
eastl::array computePipelineCreateInfo = { // eastl::array computePipelineCreateInfo = {
vk::ComputePipelineCreateInfo{ // vk::ComputePipelineCreateInfo{
.stage = // .stage =
{ // {
.stage = vk::ShaderStageFlagBits::eCompute, // .stage = vk::ShaderStageFlagBits::eCompute,
.module = eqRectToCubeShader, // .module = eqRectToCubeShader,
.pName = "main", // .pName = "main",
}, // },
.layout = pipelineLayout, // .layout = pipelineLayout,
}, // },
vk::ComputePipelineCreateInfo{ // vk::ComputePipelineCreateInfo{
.stage = // .stage =
{ // {
.stage = vk::ShaderStageFlagBits::eCompute, // .stage = vk::ShaderStageFlagBits::eCompute,
.module = diffuseRadianceShader, // .module = diffuseRadianceShader,
.pName = "main", // .pName = "main",
}, // },
.layout = pipelineLayout, // .layout = pipelineLayout,
}, // },
vk::ComputePipelineCreateInfo{ // vk::ComputePipelineCreateInfo{
.stage = // .stage =
{ // {
.stage = vk::ShaderStageFlagBits::eCompute, // .stage = vk::ShaderStageFlagBits::eCompute,
.module = prefilterShader, // .module = prefilterShader,
.pName = "main", // .pName = "main",
}, // },
.layout = pipelineLayout, // .layout = pipelineLayout,
}, // },
vk::ComputePipelineCreateInfo{ // vk::ComputePipelineCreateInfo{
.stage = // .stage =
{ // {
.stage = vk::ShaderStageFlagBits::eCompute, // .stage = vk::ShaderStageFlagBits::eCompute,
.module = brdfLutShader, // .module = brdfLutShader,
.pName = "main", // .pName = "main",
}, // },
.layout = pipelineLayout, // .layout = pipelineLayout,
}, // },
}; // };
eastl::array<vk::Pipeline, computePipelineCreateInfo.size()> pipelines; // eastl::array<vk::Pipeline, computePipelineCreateInfo.size()> pipelines;
AbortIfFailed(pDevice->m_Device.createComputePipelines(pDevice->m_PipelineCache, Cast<u32>(computePipelineCreateInfo.size()), // AbortIfFailed(pDevice->m_Device.createComputePipelines(
computePipelineCreateInfo.data(), nullptr, // pDevice->m_PipelineCache, static_cast<u32>(computePipelineCreateInfo.size()),
pipelines.data())); // computePipelineCreateInfo.data(), nullptr, pipelines.data()));
vk::Pipeline eqRectToCubePipeline = pipelines[0]; // vk::Pipeline eqRectToCubePipeline = pipelines[0];
vk::Pipeline diffuseIrradiancePipeline = pipelines[1]; // vk::Pipeline diffuseIrradiancePipeline = pipelines[1];
vk::Pipeline prefilterPipeline = pipelines[2]; // vk::Pipeline prefilterPipeline = pipelines[2];
vk::Pipeline brdfLutPipeline = pipelines[3]; // vk::Pipeline brdfLutPipeline = pipelines[3];
for (auto &createInfos : computePipelineCreateInfo) Pipeline eqRectToCubePipeline;
if (auto result =
device.CreateComputePipeline(eqRectToCubePipeline, {
.m_Shader =
{
.m_ShaderFile = EQUIRECT_TO_CUBE_SHADER_FILE,
.m_EntryPoints = {"main"},
},
.m_Name = "EqRect -> Cubemap",
}))
{ {
pDevice->m_Device.destroy(createInfos.stage.module, nullptr); ERROR("EqRect -> Cubemap Pipeline Creation failed. Cause: {}", result.What()) THEN_ABORT(result.Value());
}
Pipeline diffuseIrradiancePipeline;
if (auto result = device.CreateComputePipeline(
diffuseIrradiancePipeline,
{{.m_ShaderFile = ENVIRONMENT_SHADER_FILE, .m_EntryPoints = {DIFFUSE_IRRADIANCE_ENTRY}},
"DiffuseIrradiance"}))
{
ERROR("Diffuse Irradiance compute pipeline creation failed. Cause: {}", result.What())
THEN_ABORT(result.Value());
}
Pipeline prefilterPipeline;
if (auto result = device.CreateComputePipeline(
prefilterPipeline,
{{.m_ShaderFile = ENVIRONMENT_SHADER_FILE, .m_EntryPoints = {PREFILTER_ENTRY}}, "Prefilter"}))
{
ERROR("Prefilter compute pipeline creation failed. Cause: {}", result.What())
THEN_ABORT(result.Value());
}
Pipeline brdfLutPipeline;
if (auto result = device.CreateComputePipeline(
brdfLutPipeline, {{.m_ShaderFile = ENVIRONMENT_SHADER_FILE, .m_EntryPoints = {BRDF_LUT_ENTRY}}, "BRDF"}))
{
ERROR("BRDF LUT compute pipeline creation failed. Cause: {}", result.What())
THEN_ABORT(result.Value());
} }
#pragma endregion #pragma endregion
@ -278,115 +329,68 @@ CreateCubeFromHdrEnv(AssetLoader *assetLoader, vk::Queue computeQueue, const u32
DiffuseIrradiancePushConstants diffuseIrradiancePushConstants = { DiffuseIrradiancePushConstants diffuseIrradiancePushConstants = {
.m_SkyboxHandle = skyboxHandle, .m_SkyboxHandle = skyboxHandle,
.m_OutputTexture = diffuseIrradianceStorageHandle, .m_OutputTexture = diffuseIrradianceStorageHandle,
.m_CubeSide = diffuseIrradiance.m_Extent.width, .m_CubeSide = diffuseIrradiance->m_Extent.width,
}; };
PrefilterPushConstants prefilterPushConstants = { PrefilterPushConstants prefilterPushConstants = {
.m_SkyboxHandle = skyboxHandle, .m_SkyboxHandle = skyboxHandle,
.m_OutputTexture = NullId{},
.m_EnvSide = cubeSide, .m_EnvSide = cubeSide,
}; };
BrdfLutPushConstants brdfLutPushConstants = { BrdfLutPushConstants brdfLutPushConstants = {
.m_OutputTexture = brdfLutStorageHandle, .m_OutputTexture = brdfLutStorageHandle,
}; };
resMan->Update(); commitManager->Update();
auto cmd = assetLoader->m_CommandBuffer; auto context = assetLoader.m_Device->CreateComputeContext();
constexpr vk::CommandBufferBeginInfo beginInfo = {.flags = vk::CommandBufferUsageFlagBits::eOneTimeSubmit};
AbortIfFailed(cmd.begin(&beginInfo));
#if !defined(ASTER_NDEBUG) context.Begin();
StackString<128> labelName = "Eqrect -> Cubemap: ";
labelName += name ? name : "<unknown env>";
vk::DebugUtilsLabelEXT label = {
.pLabelName = labelName.c_str(),
.color = std::array{1.0f, 1.0f, 1.0f, 1.0f},
};
cmd.beginDebugUtilsLabelEXT(&label);
#endif
cmd.pipelineBarrier2(&readyToWriteDependency); context.BeginDebugRegion("Eqrect -> Cubemap");
cmd.bindDescriptorSets(vk::PipelineBindPoint::eCompute, pipelineLayout, 0, 1, &resMan->m_DescriptorSet, 0, nullptr); context.Dependency(readyToWriteDependency);
cmd.bindPipeline(vk::PipelineBindPoint::eCompute, eqRectToCubePipeline);
cmd.pushConstants(pipelineLayout, vk::ShaderStageFlagBits::eCompute, 0, sizeof skyboxPushConstant,
&skyboxPushConstant);
assert(skybox.m_Extent.width % 16 == 0 && skybox.m_Extent.height % 16 == 0);
cmd.dispatch(skybox.m_Extent.width / 16, skybox.m_Extent.height / 16, 6);
GenerateMipMaps(cmd, &skybox, vk::ImageLayout::eGeneral, vk::ImageLayout::eGeneral, assert(skybox->m_Extent.width % 16 == 0 && skybox->m_Extent.height % 16 == 0);
context.Dispatch(eqRectToCubePipeline, skybox->m_Extent.width / 16, skybox->m_Extent.height / 16, 6,
skyboxPushConstant);
GenerateMipMaps(context, skybox, vk::ImageLayout::eGeneral, vk::ImageLayout::eGeneral,
vk::PipelineStageFlagBits2::eComputeShader, vk::PipelineStageFlagBits2::eComputeShader); vk::PipelineStageFlagBits2::eComputeShader, vk::PipelineStageFlagBits2::eComputeShader);
cmd.bindPipeline(vk::PipelineBindPoint::eCompute, diffuseIrradiancePipeline); assert(diffuseIrradiance->m_Extent.width % 16 == 0 && diffuseIrradiance->m_Extent.height % 16 == 0);
cmd.pushConstants(pipelineLayout, vk::ShaderStageFlagBits::eCompute, 0, sizeof skyboxPushConstant, context.Dispatch(diffuseIrradiancePipeline, diffuseIrradiance->m_Extent.width / 16,
&diffuseIrradiancePushConstants); diffuseIrradiance->m_Extent.width / 16, 6, diffuseIrradiancePushConstants);
assert(diffuseIrradiance.m_Extent.width % 16 == 0 && diffuseIrradiance.m_Extent.height % 16 == 0);
cmd.dispatch(diffuseIrradiance.m_Extent.width / 16, diffuseIrradiance.m_Extent.width / 16, 6);
cmd.pipelineBarrier2(&diffIrrToSampleDependency); context.Dependency(diffIrrToSampleDependency);
cmd.bindPipeline(vk::PipelineBindPoint::eCompute, prefilterPipeline); u32 mipSize = prefilterCube->m_Extent.width;
u32 mipSize = prefilterCube.m_Extent.width;
assert(mipSize % 16 == 0); assert(mipSize % 16 == 0);
for (u32 mipCount = 0; auto &tex : prefilterStorageHandles) for (u32 mipCount = 0; auto &tex : prefilterStorageHandles)
{ {
prefilterPushConstants.m_OutputTexture = tex; prefilterPushConstants.m_OutputTexture = tex;
prefilterPushConstants.m_CubeSide = mipSize; prefilterPushConstants.m_CubeSide = mipSize;
prefilterPushConstants.m_Roughness = Cast<f32>(mipCount) / Cast<f32>(prefilterMipCountMax); prefilterPushConstants.m_Roughness = static_cast<f32>(mipCount) / static_cast<f32>(prefilterMipCountMax - 1);
cmd.pushConstants(pipelineLayout, vk::ShaderStageFlagBits::eCompute, 0, sizeof prefilterPushConstants,
&prefilterPushConstants);
u32 groupCount = eastl::max(mipSize / 16u, 1u); u32 groupCount = eastl::max(mipSize / 16u, 1u);
cmd.dispatch(groupCount, groupCount, 6); context.Dispatch(prefilterPipeline, groupCount, groupCount, 6, prefilterPushConstants);
++mipCount; ++mipCount;
mipSize = mipSize >> 1; mipSize = mipSize >> 1;
} }
cmd.pipelineBarrier2(&skyboxToSampleDependency); context.Dependency(skyboxToSampleDependency);
cmd.pipelineBarrier2(&prefilterToSampleDependency); context.Dependency(prefilterToSampleDependency);
cmd.bindPipeline(vk::PipelineBindPoint::eCompute, brdfLutPipeline); assert(brdfLut->m_Extent.width % 16 == 0 && brdfLut->m_Extent.height % 16 == 0);
cmd.pushConstants(pipelineLayout, vk::ShaderStageFlagBits::eCompute, 0, sizeof brdfLutPushConstants, context.Dispatch(brdfLutPipeline, brdfLut->m_Extent.width / 16, brdfLut->m_Extent.height / 16, 1,
&brdfLutPushConstants); brdfLutPushConstants);
assert(brdfLut.m_Extent.width % 16 == 0 && brdfLut.m_Extent.height % 16 == 0);
cmd.dispatch(brdfLut.m_Extent.width / 16, brdfLut.m_Extent.height / 16, 1);
#if !defined(ASTER_NDEBUG) context.EndDebugRegion();
cmd.endDebugUtilsLabelEXT();
#endif
AbortIfFailed(cmd.end()); context.End();
vk::SubmitInfo submitInfo = { auto receipt = device.Submit(context);
.waitSemaphoreCount = 0, device.WaitOn(receipt);
.pWaitDstStageMask = nullptr,
.commandBufferCount = 1,
.pCommandBuffers = &cmd,
};
vk::Fence fence;
vk::FenceCreateInfo fenceCreateInfo = {};
AbortIfFailed(pDevice->m_Device.createFence(&fenceCreateInfo, nullptr, &fence));
AbortIfFailed(computeQueue.submit(1, &submitInfo, fence));
AbortIfFailed(pDevice->m_Device.waitForFences(1, &fence, true, MaxValue<u32>));
pDevice->m_Device.destroy(fence, nullptr);
AbortIfFailed(pDevice->m_Device.resetCommandPool(assetLoader->m_CommandPool, {}));
skybox = {};
resMan->Release(skyboxStorageHandle);
resMan->Release(diffuseIrradianceStorageHandle);
resMan->Release(brdfLutStorageHandle);
for (auto &texHandles : prefilterStorageHandles)
{
StorageTextureCube st;
resMan->Release(&st, texHandles);
pDevice->m_Device.destroy(st.m_View, nullptr);
}
for (auto &pipeline : pipelines)
{
pDevice->m_Device.destroy(pipeline, nullptr);
}
pDevice->m_Device.destroy(pipelineLayout, nullptr);
return { return {
.m_Skybox = skyboxHandle, .m_Skybox = skyboxHandle,

View File

@ -1,28 +1,28 @@
// ============================================= // =============================================
// Aster: ibl_helpers.h // Aster: ibl_helpers.h
// Copyright (c) 2020-2024 Anish Bhobe // Copyright (c) 2020-2025 Anish Bhobe
// ============================================= // =============================================
#pragma once #pragma once
#include "aster/aster.h" #include "aster/aster.h"
#include "gpu_resource_manager.h" #include "aster/import_types.h"
namespace aster
{
struct Pipeline; struct Pipeline;
struct Texture; struct Texture;
struct TextureCube; struct TextureCube;
} // namespace aster
struct AssetLoader; struct AssetLoader;
struct Environment struct Environment
{ {
TextureHandle m_Skybox; aster::ResId<aster::TextureView> m_Skybox;
TextureHandle m_Diffuse; aster::ResId<aster::TextureView> m_Diffuse;
TextureHandle m_Prefilter; aster::ResId<aster::TextureView> m_Prefilter;
TextureHandle m_BrdfLut; aster::ResId<aster::TextureView> m_BrdfLut;
void Destroy(GpuResourceManager *resourceManager);
}; };
Environment Environment CreateCubeFromHdrEnv(AssetLoader &assetLoader, u32 cubeSide, aster::ResId<aster::TextureView> hdrEnv);
CreateCubeFromHdrEnv(AssetLoader *assetLoader, vk::Queue computeQueue, u32 cubeSide, TextureHandle hdrEnv,
cstr name = nullptr);

View File

@ -1,35 +1,16 @@
// ============================================= // =============================================
// Aster: light_manager.cpp // Aster: light_manager.cpp
// Copyright (c) 2020-2024 Anish Bhobe // Copyright (c) 2020-2025 Anish Bhobe
// ============================================= // =============================================
#include "light_manager.h" #include "light_manager.h"
#include "aster/core/buffer.h" #include "aster/core/buffer.h"
#include "aster/systems/commit_manager.h"
#include "aster/systems/rendering_device.h"
#include "aster/systems/resource.h"
#include "glm/ext/matrix_transform.hpp" #include "glm/ext/matrix_transform.hpp"
struct Light
{
union {
vec3 um_Position;
vec3 um_Direction;
};
f32 m_Range; // < 0.0 for invalid
u32 m_Color_; // LSB is used for flags. (R G B Flags)
f32 m_Intensity;
constexpr static u32 MAX_GEN = 0x40;
constexpr static u32 GEN_MASK = MAX_GEN - 1;
constexpr static u32 TYPE_MASK = 0xC0;
constexpr static u32 TYPE_INVALID = 0x0;
constexpr static u32 TYPE_DIRECTIONAL = 1 << 6;
constexpr static u32 TYPE_POINT = 2 << 6;
constexpr static u32 TYPE_SPOT = 3 << 6; // Currently Unused
constexpr static u32 COLOR_MASK = ~(GEN_MASK | TYPE_MASK);
};
// Static Checks // Static Checks
// Ensure layouts are exact. // Ensure layouts are exact.
@ -53,29 +34,29 @@ static_assert((Light::TYPE_MASK & Light::TYPE_SPOT) == Light::TYPE_SPOT);
static_assert(Light::COLOR_MASK == 0xFFFFFF00); static_assert(Light::COLOR_MASK == 0xFFFFFF00);
inline u32 inline u32
ToColor32(const vec4 &col) ToColor32(vec4 const &col)
{ {
const u32 r = Cast<u32>(eastl::min(col.r, 1.0f) * 255.99f); u32 const r = static_cast<u32>(eastl::min(col.r, 1.0f) * 255.99f);
const u32 g = Cast<u32>(eastl::min(col.g, 1.0f) * 255.99f); u32 const g = static_cast<u32>(eastl::min(col.g, 1.0f) * 255.99f);
const u32 b = Cast<u32>(eastl::min(col.b, 1.0f) * 255.99f); u32 const b = static_cast<u32>(eastl::min(col.b, 1.0f) * 255.99f);
const u32 a = Cast<u32>(eastl::min(col.a, 1.0f) * 255.99f); u32 const a = static_cast<u32>(eastl::min(col.a, 1.0f) * 255.99f);
return r << 24 | g << 16 | b << 8 | a; return r << 24 | g << 16 | b << 8 | a;
} }
inline u32 inline u32
ToColor32(const vec3 &col) ToColor32(vec3 const &col)
{ {
const u32 r = Cast<u32>(eastl::min(col.r, 1.0f) * 255.99f); u32 const r = static_cast<u32>(eastl::min(col.r, 1.0f) * 255.99f);
const u32 g = Cast<u32>(eastl::min(col.g, 1.0f) * 255.99f); u32 const g = static_cast<u32>(eastl::min(col.g, 1.0f) * 255.99f);
const u32 b = Cast<u32>(eastl::min(col.b, 1.0f) * 255.99f); u32 const b = static_cast<u32>(eastl::min(col.b, 1.0f) * 255.99f);
constexpr u32 a = 255; constexpr u32 a = 255;
return r << 24 | g << 16 | b << 8 | a; return r << 24 | g << 16 | b << 8 | a;
} }
LightManager::LightManager(GpuResourceManager *resourceManager) LightManager::LightManager(aster::RenderingDevice &device)
: m_ResourceManager{resourceManager} : m_Device{&device}
, m_DirectionalLightCount{} , m_DirectionalLightCount{}
, m_PointLightCount{} , m_PointLightCount{}
, m_MetaInfo{} , m_MetaInfo{}
@ -83,41 +64,10 @@ LightManager::LightManager(GpuResourceManager *resourceManager)
{ {
} }
LightManager::~LightManager()
{
m_ResourceManager->Release(m_MetaInfo.m_LightBuffer);
}
LightManager::LightManager(LightManager &&other) noexcept
: m_ResourceManager(other.m_ResourceManager)
, m_Lights(std::move(other.m_Lights))
, m_DirectionalLightCount(other.m_DirectionalLightCount)
, m_PointLightCount(other.m_PointLightCount)
, m_MetaInfo(other.m_MetaInfo)
, m_GpuBufferCapacity_(other.m_GpuBufferCapacity_)
{
other.m_MetaInfo.m_LightBuffer = {};
}
LightManager &
LightManager::operator=(LightManager &&other) noexcept
{
if (this == &other)
return *this;
m_ResourceManager = other.m_ResourceManager;
m_Lights = std::move(other.m_Lights);
m_DirectionalLightCount = other.m_DirectionalLightCount;
m_PointLightCount = other.m_PointLightCount;
m_MetaInfo = other.m_MetaInfo;
other.m_MetaInfo.m_LightBuffer = {};
m_GpuBufferCapacity_ = other.m_GpuBufferCapacity_;
return *this;
}
LightHandle LightHandle
LightManager::AddDirectional(const vec3 &direction, const vec3 &color, f32 intensity) LightManager::AddDirectional(vec3 const &direction, vec3 const &color, f32 intensity)
{ {
const vec3 normDirection = normalize(direction); vec3 const normDirection = normalize(direction);
if (m_DirectionalLightCount < m_MetaInfo.m_DirectionalLightMaxCount) if (m_DirectionalLightCount < m_MetaInfo.m_DirectionalLightMaxCount)
{ {
u16 index = 0; u16 index = 0;
@ -125,7 +75,7 @@ LightManager::AddDirectional(const vec3 &direction, const vec3 &color, f32 inten
{ {
if (light.m_Range < 0) if (light.m_Range < 0)
{ {
const u8 gen = light.m_Color_ & Light::GEN_MASK; u8 const gen = light.m_Color_ & Light::GEN_MASK;
light.m_Color_ = (ToColor32(color) & Light::COLOR_MASK) | Light::TYPE_DIRECTIONAL | gen; light.m_Color_ = (ToColor32(color) & Light::COLOR_MASK) | Light::TYPE_DIRECTIONAL | gen;
light.m_Range = 1.0f; light.m_Range = 1.0f;
@ -145,8 +95,8 @@ LightManager::AddDirectional(const vec3 &direction, const vec3 &color, f32 inten
if (m_DirectionalLightCount == m_MetaInfo.m_DirectionalLightMaxCount && if (m_DirectionalLightCount == m_MetaInfo.m_DirectionalLightMaxCount &&
m_MetaInfo.m_DirectionalLightMaxCount == m_MetaInfo.m_PointLightOffset) m_MetaInfo.m_DirectionalLightMaxCount == m_MetaInfo.m_PointLightOffset)
{ {
const u16 oldPointLightOffset = m_MetaInfo.m_PointLightOffset; u16 const oldPointLightOffset = m_MetaInfo.m_PointLightOffset;
const u32 pointLightMaxCount = m_MetaInfo.m_PointLightMaxCount; u32 const pointLightMaxCount = m_MetaInfo.m_PointLightMaxCount;
// Might cause a capacity increase, but I want to use that for my gpu buffer resize. // Might cause a capacity increase, but I want to use that for my gpu buffer resize.
m_Lights.push_back(); m_Lights.push_back();
m_Lights.push_back(); m_Lights.push_back();
@ -172,7 +122,7 @@ LightManager::AddDirectional(const vec3 &direction, const vec3 &color, f32 inten
m_Lights[m_DirectionalLightCount].m_Range = 1.0f; m_Lights[m_DirectionalLightCount].m_Range = 1.0f;
m_Lights[m_DirectionalLightCount].um_Direction = normDirection; m_Lights[m_DirectionalLightCount].um_Direction = normDirection;
m_Lights[m_DirectionalLightCount].m_Intensity = intensity; m_Lights[m_DirectionalLightCount].m_Intensity = intensity;
const u16 index = m_DirectionalLightCount; u16 const index = m_DirectionalLightCount;
++m_DirectionalLightCount; ++m_DirectionalLightCount;
++m_MetaInfo.m_DirectionalLightMaxCount; ++m_MetaInfo.m_DirectionalLightMaxCount;
@ -181,7 +131,7 @@ LightManager::AddDirectional(const vec3 &direction, const vec3 &color, f32 inten
} }
LightHandle LightHandle
LightManager::AddPoint(const vec3 &position, const vec3 &color, const f32 radius, f32 intensity) LightManager::AddPoint(vec3 const &position, vec3 const &color, f32 const radius, f32 intensity)
{ {
assert(m_PointLightCount <= m_MetaInfo.m_PointLightMaxCount); assert(m_PointLightCount <= m_MetaInfo.m_PointLightMaxCount);
assert(radius >= 0.0f); assert(radius >= 0.0f);
@ -192,7 +142,7 @@ LightManager::AddPoint(const vec3 &position, const vec3 &color, const f32 radius
{ {
if (light->m_Range < 0) if (light->m_Range < 0)
{ {
const u8 gen = light->m_Color_ & Light::GEN_MASK; u8 const gen = light->m_Color_ & Light::GEN_MASK;
light->m_Color_ = (ToColor32(color) & Light::COLOR_MASK) | Light::TYPE_POINT | gen; light->m_Color_ = (ToColor32(color) & Light::COLOR_MASK) | Light::TYPE_POINT | gen;
light->m_Range = radius; light->m_Range = radius;
@ -201,7 +151,7 @@ LightManager::AddPoint(const vec3 &position, const vec3 &color, const f32 radius
m_GpuBufferCapacity_ |= UPDATE_REQUIRED_BIT; m_GpuBufferCapacity_ |= UPDATE_REQUIRED_BIT;
return {Light::TYPE_POINT, gen, Cast<u16>(index)}; return {Light::TYPE_POINT, gen, static_cast<u16>(index)};
} }
++light; ++light;
} }
@ -210,7 +160,7 @@ LightManager::AddPoint(const vec3 &position, const vec3 &color, const f32 radius
} }
m_Lights.push_back(); m_Lights.push_back();
const u16 index = m_PointLightCount; u16 const index = m_PointLightCount;
Light *light = &m_Lights[index + m_MetaInfo.m_PointLightOffset]; Light *light = &m_Lights[index + m_MetaInfo.m_PointLightOffset];
constexpr u8 gen = 0; // New light constexpr u8 gen = 0; // New light
@ -231,31 +181,29 @@ LightManager::AddPoint(const vec3 &position, const vec3 &color, const f32 radius
void void
LightManager::Update() LightManager::Update()
{ {
const u16 requiredBufferCapacity = eastl::min(Cast<u16>(m_Lights.capacity()), MAX_LIGHTS); u16 const requiredBufferCapacity = eastl::min(static_cast<u16>(m_Lights.capacity()), MAX_LIGHTS);
if ((m_GpuBufferCapacity_ & CAPACITY_MASK) < requiredBufferCapacity) if ((m_GpuBufferCapacity_ & CAPACITY_MASK) < requiredBufferCapacity)
{ {
StorageBuffer newBuffer; m_LightBuffer = m_Device->CreateStorageBuffer(requiredBufferCapacity * sizeof m_Lights[0], "Light Buffer");
newBuffer.Init(m_ResourceManager->m_Device, requiredBufferCapacity * sizeof m_Lights[0], true, "Light Buffer");
m_GpuBufferCapacity_ = requiredBufferCapacity | UPDATE_REQUIRED_BIT; m_GpuBufferCapacity_ = requiredBufferCapacity | UPDATE_REQUIRED_BIT;
m_ResourceManager->Release(m_MetaInfo.m_LightBuffer); m_MetaInfo.m_LightBuffer = m_LightBuffer->GetDeviceAddress();
m_MetaInfo.m_LightBuffer = m_ResourceManager->Commit(&newBuffer);
} }
if (m_GpuBufferCapacity_ & UPDATE_REQUIRED_BIT) if (m_GpuBufferCapacity_ & UPDATE_REQUIRED_BIT)
{ {
m_ResourceManager->Write(m_MetaInfo.m_LightBuffer, 0, m_Lights.size() * sizeof m_Lights[0], m_Lights.data()); m_LightBuffer->Write(0, m_Lights.size() * sizeof m_Lights[0], m_Lights.data());
} }
} }
void void
LightManager::RemoveLight(const LightHandle handle) LightManager::RemoveLight(LightHandle const handle)
{ {
const u8 handleGen = handle.m_Generation; u8 const handleGen = handle.m_Generation;
if (handle.m_Type == Light::TYPE_DIRECTIONAL) if (handle.m_Type == Light::TYPE_DIRECTIONAL)
{ {
Light *lightSlot = &m_Lights[handle.m_Index]; Light *lightSlot = &m_Lights[handle.m_Index];
const u8 slotGen = lightSlot->m_Color_ & Light::GEN_MASK; u8 const slotGen = lightSlot->m_Color_ & Light::GEN_MASK;
if (slotGen > handleGen) if (slotGen > handleGen)
{ {
WARN("Invalid handle gen: {} being freed. (slot gen: {})", handleGen, slotGen); WARN("Invalid handle gen: {} being freed. (slot gen: {})", handleGen, slotGen);
@ -270,7 +218,7 @@ LightManager::RemoveLight(const LightHandle handle)
if (handle.m_Type == Light::TYPE_POINT) if (handle.m_Type == Light::TYPE_POINT)
{ {
Light *lightSlot = &m_Lights[handle.m_Index + m_MetaInfo.m_PointLightOffset]; Light *lightSlot = &m_Lights[handle.m_Index + m_MetaInfo.m_PointLightOffset];
const u8 slotGen = lightSlot->m_Color_ & Light::GEN_MASK; u8 const slotGen = lightSlot->m_Color_ & Light::GEN_MASK;
if (slotGen > handleGen) if (slotGen > handleGen)
{ {
WARN("Invalid handle gen: {} being freed. (slot gen: {})", handleGen, slotGen); WARN("Invalid handle gen: {} being freed. (slot gen: {})", handleGen, slotGen);

View File

@ -1,14 +1,24 @@
// ============================================= // =============================================
// Aster: light_manager.h // Aster: light_manager.h
// Copyright (c) 2020-2024 Anish Bhobe // Copyright (c) 2020-2025 Anish Bhobe
// ============================================= // =============================================
#pragma once #pragma once
#include "aster/aster.h" #include "aster/aster.h"
#include "aster/import_types.h"
// TODO: Separate files so you only import handles. // TODO: Separate files so you only import handles.
#include "gpu_resource_manager.h" #include "aster/systems/resource.h"
#include <EASTL/vector.h>
namespace aster
{
class RenderingDevice;
class ResourceManager;
class CommitManager;
} // namespace systems
struct DirectionalLight struct DirectionalLight
{ {
@ -33,26 +43,51 @@ struct LightHandle
u16 m_Index; u16 m_Index;
}; };
struct Light; struct Light
{
union {
vec3 um_Position;
vec3 um_Direction;
};
f32 m_Range; // < 0.0 for invalid
u32 m_Color_; // LSB is used for flags. (R G B Flags)
f32 m_Intensity;
u32 m_Pad0;
u32 m_Pad1;
constexpr static u32 MAX_GEN = 0x40;
constexpr static u32 GEN_MASK = MAX_GEN - 1;
constexpr static u32 TYPE_MASK = 0xC0;
constexpr static u32 TYPE_INVALID = 0x0;
constexpr static u32 TYPE_DIRECTIONAL = 1 << 6;
constexpr static u32 TYPE_POINT = 2 << 6;
constexpr static u32 TYPE_SPOT = 3 << 6; // Currently Unused
constexpr static u32 COLOR_MASK = ~(GEN_MASK | TYPE_MASK);
};
struct LightManager struct LightManager
{ {
constexpr static u16 MAX_LIGHTS = MaxValue<u16>; constexpr static u16 MAX_LIGHTS = MaxValue<u16>;
struct LightMetaInfo struct LightMetaInfo
{ {
// The number of directional lights is relatively low (1 - 2) and will almost never change in a scene. // The number of directional lights is relatively low (1 - 2) and will almost never change in a scene.
// We can use that with Offset = 0, and point light at further offsets. // We can use that with Offset = 0, and point light at further offsets.
// This way we don't need to move point lights often. // This way we don't need to move point lights often.
BufferHandle m_LightBuffer; // 04 04 uptr m_LightBuffer; // 08 08
u16 m_PointLightMaxCount; // 02 06 u16 m_PointLightMaxCount; // 02 10
u16 m_PointLightOffset; // 02 08 u16 m_PointLightOffset; // 02 12
u16 m_DirectionalLightMaxCount; // 02 10 u16 m_DirectionalLightMaxCount; // 02 14
u16 m_UnusedPadding0 = 0; // 02 12 u16 m_UnusedPadding0 = 0; // 02 16
}; };
GpuResourceManager *m_ResourceManager; aster::RenderingDevice *m_Device;
eastl::vector<Light> m_Lights; eastl::vector<Light> m_Lights;
aster::Ref<aster::Buffer> m_LightBuffer;
// We don't need a Directional Light free list. We will just brute force iterate. // We don't need a Directional Light free list. We will just brute force iterate.
u16 m_DirectionalLightCount; u16 m_DirectionalLightCount;
@ -66,18 +101,18 @@ struct LightManager
// Using lower bit. Capacity can be directly a multiple of 2 // Using lower bit. Capacity can be directly a multiple of 2
// Thus, range is up to MaxValue<u16> // Thus, range is up to MaxValue<u16>
constexpr static u16 UPDATE_REQUIRED_BIT = 1; constexpr static u16 UPDATE_REQUIRED_BIT = 1;
constexpr static u16 CAPACITY_MASK = Cast<u16>(~UPDATE_REQUIRED_BIT); constexpr static u16 CAPACITY_MASK = static_cast<u16>(~UPDATE_REQUIRED_BIT);
LightHandle AddDirectional(const vec3 &direction, const vec3 &color, f32 intensity); LightHandle AddDirectional(const vec3 &direction, const vec3 &color, f32 intensity);
LightHandle AddPoint(const vec3 &position, const vec3 &color, f32 radius, f32 intensity); LightHandle AddPoint(const vec3 &position, const vec3 &color, f32 radius, f32 intensity);
void Update(); void Update();
void RemoveLight(LightHandle handle); void RemoveLight(LightHandle handle);
explicit LightManager(GpuResourceManager *resourceManager); ~LightManager() = default;
~LightManager();
LightManager(LightManager &&other) noexcept; explicit LightManager(aster::RenderingDevice &device);
LightManager &operator=(LightManager &&other) noexcept; LightManager(LightManager &&other) noexcept = default;
LightManager &operator=(LightManager &&other) noexcept = default;
DISALLOW_COPY_AND_ASSIGN(LightManager); DISALLOW_COPY_AND_ASSIGN(LightManager);
}; };

Binary file not shown.

Some files were not shown because too many files have changed in this diff Show More