Compare commits

...

25 Commits

Author SHA1 Message Date
Anish Bhobe 1a8f113323 Vertex buffers. 2024-07-08 16:06:09 +02:00
Anish Bhobe 7c17d09822 Cross compatibility with older linux SDK. 2024-07-08 16:06:09 +02:00
Anish Bhobe 363259a52e Fix sync bug. 2024-07-05 12:48:08 +02:00
Anish Bhobe c338c34337 Added buffer. 2024-07-05 00:51:33 +02:00
Anish Bhobe 0ca2779014 Refactoring and extracting helpers. 2024-07-01 21:51:07 +02:00
Anish Bhobe d9b0e82be7 Improve HLSL compile. 2024-07-01 21:22:40 +02:00
Anish Bhobe ccb0aa5fbe File hierarchy cleanup. 2024-07-01 20:19:51 +02:00
Anish Bhobe 5744e7a13c Trivial Pipeline object. 2024-07-01 19:53:06 +02:00
Anish Bhobe eaf4556bad Triangle rendered to screen. 2024-07-01 18:37:08 +02:00
Anish Bhobe c6ff2f4f76 Clear Screen. 2024-06-29 20:48:16 +02:00
Anish Bhobe 76a7927643 Large cleanup. 2024-06-29 11:51:37 +02:00
Anish Bhobe 8769215437 Removed duplication. 2024-06-27 01:36:17 +02:00
Anish Bhobe e120b38066 Cleaned up result formatting. 2024-06-26 20:25:24 +02:00
Anish Bhobe e55f30e7e7 Swapchain added. 2024-06-26 19:49:55 +02:00
Anish Bhobe c16456c610 Refactored Device and Cleaned up includes. 2024-06-26 19:49:46 +02:00
Anish Bhobe f603bd5752 Refactored for new coding scheme. No exceptions. 2024-06-26 18:13:58 +02:00
Anish Bhobe 7bf87f5127 Device creation + cleanup. 2024-06-16 21:27:43 +02:00
Anish Bhobe ce4dd9b096 Small fixes for compat with clang. 2024-06-16 19:08:14 +02:00
Anish Bhobe 44173ffdbc Macro to change min-log-level. 2024-06-16 11:49:05 +02:00
Anish Bhobe 2f4db7ffaf Physical Device creation complete. 2024-06-16 11:47:29 +02:00
Anish Bhobe 20cf0876eb Purged custom formatting code. 2024-06-16 11:01:26 +02:00
Anish Bhobe 7f66176895 Moved to exceptions + physical device. 2024-06-16 11:00:45 +02:00
Anish Bhobe 39732c1a27 Project reorganization. 2024-06-15 18:29:47 +02:00
Anish Bhobe 8b0a7f2622 Format Code and cleanup. 2024-06-15 17:56:34 +02:00
Anish Bhobe a0a84f30f8 Improved logging based of fmtlib. 2024-06-15 17:45:22 +02:00
49 changed files with 2532 additions and 1086 deletions

View File

@ -3,197 +3,25 @@
# chosen value in case the base style changes (last sync: Clang 14.0).
---
### General config, applies to all languages ###
BasedOnStyle: LLVM
AccessModifierOffset: -4
AlignAfterOpenBracket: DontAlign
# AlignArrayOfStructures: None
# AlignConsecutiveMacros: None
# AlignConsecutiveAssignments: None
# AlignConsecutiveBitFields: None
# AlignConsecutiveDeclarations: None
# AlignEscapedNewlines: Right
AlignOperands: DontAlign
AlignTrailingComments: false
# AllowAllArgumentsOnNextLine: true
AllowAllParametersOfDeclarationOnNextLine: false
# AllowShortEnumsOnASingleLine: true
# AllowShortBlocksOnASingleLine: Never
# AllowShortCaseLabelsOnASingleLine: false
# AllowShortFunctionsOnASingleLine: All
# AllowShortLambdasOnASingleLine: All
# AllowShortIfStatementsOnASingleLine: Never
# AllowShortLoopsOnASingleLine: false
# AlwaysBreakAfterDefinitionReturnType: None
# AlwaysBreakAfterReturnType: None
# AlwaysBreakBeforeMultilineStrings: false
# AlwaysBreakTemplateDeclarations: MultiLine
# AttributeMacros:
# - __capability
# BinPackArguments: true
# BinPackParameters: true
# BraceWrapping:
# AfterCaseLabel: false
# AfterClass: false
# AfterControlStatement: Never
# AfterEnum: false
# AfterFunction: false
# AfterNamespace: false
# AfterObjCDeclaration: false
# AfterStruct: false
# AfterUnion: false
# AfterExternBlock: false
# BeforeCatch: false
# BeforeElse: false
# BeforeLambdaBody: false
# BeforeWhile: false
# IndentBraces: false
# SplitEmptyFunction: true
# SplitEmptyRecord: true
# SplitEmptyNamespace: true
# BreakBeforeBinaryOperators: None
# BreakBeforeConceptDeclarations: true
# BreakBeforeBraces: Attach
# BreakBeforeInheritanceComma: false
# BreakInheritanceList: BeforeColon
# BreakBeforeTernaryOperators: true
# BreakConstructorInitializersBeforeComma: false
BreakConstructorInitializers: AfterColon
# BreakStringLiterals: true
ColumnLimit: 0
# CommentPragmas: '^ IWYU pragma:'
# QualifierAlignment: Leave
# CompactNamespaces: false
ConstructorInitializerIndentWidth: 8
ContinuationIndentWidth: 8
Cpp11BracedListStyle: false
# DeriveLineEnding: true
# DerivePointerAlignment: false
# DisableFormat: false
# EmptyLineAfterAccessModifier: Never
# EmptyLineBeforeAccessModifier: LogicalBlock
# ExperimentalAutoDetectBinPacking: false
# PackConstructorInitializers: BinPack
ConstructorInitializerAllOnOneLineOrOnePerLine: true
# AllowAllConstructorInitializersOnNextLine: true
# FixNamespaceComments: true
# ForEachMacros:
# - foreach
# - Q_FOREACH
# - BOOST_FOREACH
# IfMacros:
# - KJ_IF_MAYBE
# IncludeBlocks: Preserve
BasedOnStyle: Microsoft
#AccessModifierOffset: -2
AlwaysBreakAfterReturnType: AllDefinitions
BreakConstructorInitializers: BeforeComma
#ColumnLimit: 0
#ConstructorInitializerIndentWidth: 4
#ConstructorInitializerAllOnOneLineOrOnePerLine: true
AlwaysBreakTemplateDeclarations: Yes
IncludeCategories:
- Regex: '".*"'
Priority: 1
- Regex: '^<.*\.h>'
Priority: 2
- Regex: '^<.*'
Priority: 3
# IncludeIsMainRegex: '(Test)?$'
# IncludeIsMainSourceRegex: ''
# IndentAccessModifiers: false
IndentCaseLabels: true
# IndentCaseBlocks: false
# IndentGotoLabels: true
# IndentPPDirectives: None
# IndentExternBlock: AfterExternBlock
# IndentRequires: false
IndentWidth: 4
# IndentWrappedFunctionNames: false
# InsertTrailingCommas: None
# JavaScriptQuotes: Leave
# JavaScriptWrapImports: true
KeepEmptyLinesAtTheStartOfBlocks: false
# LambdaBodyIndentation: Signature
# MacroBlockBegin: ''
# MacroBlockEnd: ''
# MaxEmptyLinesToKeep: 1
# NamespaceIndentation: None
# PenaltyBreakAssignment: 2
# PenaltyBreakBeforeFirstCallParameter: 19
# PenaltyBreakComment: 300
# PenaltyBreakFirstLessLess: 120
# PenaltyBreakOpenParenthesis: 0
# PenaltyBreakString: 1000
# PenaltyBreakTemplateDeclaration: 10
# PenaltyExcessCharacter: 1000000
# PenaltyReturnTypeOnItsOwnLine: 60
# PenaltyIndentedWhitespace: 0
# PointerAlignment: Right
# PPIndentWidth: -1
# ReferenceAlignment: Pointer
# ReflowComments: true
# RemoveBracesLLVM: false
# SeparateDefinitionBlocks: Leave
# ShortNamespaceLines: 1
# SortIncludes: CaseSensitive
# SortJavaStaticImport: Before
# SortUsingDeclarations: true
# SpaceAfterCStyleCast: false
# SpaceAfterLogicalNot: false
# SpaceAfterTemplateKeyword: true
# SpaceBeforeAssignmentOperators: true
# SpaceBeforeCaseColon: false
# SpaceBeforeCpp11BracedList: false
# SpaceBeforeCtorInitializerColon: true
# SpaceBeforeInheritanceColon: true
# SpaceBeforeParens: ControlStatements
# SpaceBeforeParensOptions:
# AfterControlStatements: true
# AfterForeachMacros: true
# AfterFunctionDefinitionName: false
# AfterFunctionDeclarationName: false
# AfterIfMacros: true
# AfterOverloadedOperator: false
# BeforeNonEmptyParentheses: false
# SpaceAroundPointerQualifiers: Default
# SpaceBeforeRangeBasedForLoopColon: true
# SpaceInEmptyBlock: false
# SpaceInEmptyParentheses: false
# SpacesBeforeTrailingComments: 1
# SpacesInAngles: Never
# SpacesInConditionalStatement: false
# SpacesInContainerLiterals: true
# SpacesInCStyleCastParentheses: false
## Godot TODO: We'll want to use a min of 1, but we need to see how to fix
## our comment capitalization at the same time.
SpacesInLineCommentPrefix:
Minimum: 0
Maximum: -1
# SpacesInParentheses: false
# SpacesInSquareBrackets: false
# SpaceBeforeSquareBrackets: false
# BitFieldColonSpacing: Both
# StatementAttributeLikeMacros:
# - Q_EMIT
# StatementMacros:
# - Q_UNUSED
# - QT_REQUIRE_VERSION
TabWidth: 4
# UseCRLF: false
UseTab: Always
# WhitespaceSensitiveMacros:
# - STRINGIZE
# - PP_STRINGIZE
# - BOOST_PP_STRINGIZE
# - NS_SWIFT_NAME
# - CF_SWIFT_NAME
---
### C++ specific config ###
Language: Cpp
Standard: c++17
---
### ObjC specific config ###
Language: ObjC
# ObjCBinPackProtocolList: Auto
ObjCBlockIndentWidth: 4
# ObjCBreakBeforeNestedBlockParam: true
# ObjCSpaceAfterProperty: false
# ObjCSpaceBeforeProtocolList: true
---
### Java specific config ###
Language: Java
# BreakAfterJavaFieldAnnotations: false
JavaImportGroups: ['org.godotengine', 'android', 'androidx', 'com.android', 'com.google', 'java', 'javax']
...
- Regex: '".*"'
Priority: 1
- Regex: '^<.*\.h>'
Priority: 2
- Regex: '^<.*'
Priority: 3
#IndentCaseLabels: true
IndentWidth: 4
#KeepEmptyLinesAtTheStartOfBlocks: false
PenaltyReturnTypeOnItsOwnLine: 0
TabWidth: 4
Language: Cpp
Standard: c++20

45
.clang-tidy Normal file
View File

@ -0,0 +1,45 @@
---
Checks: 'clang-diagnostic-*,clang-analyzer-*,-*,cppcoreguidelines-pro-type-member-init,modernize-redundant-void-arg,modernize-use-bool-literals,modernize-use-default-member-init,modernize-use-nullptr,readability-braces-around-statements,readability-redundant-member-init'
WarningsAsErrors: ''
HeaderFilterRegex: ''
FormatStyle: none
CheckOptions:
- key: cert-dcl16-c.NewSuffixes
value: 'L;LL;LU;LLU'
- key: cert-oop54-cpp.WarnOnlyIfThisHasSuspiciousField
value: '0'
- key: cppcoreguidelines-explicit-virtual-functions.IgnoreDestructors
value: '1'
- key: cppcoreguidelines-non-private-member-variables-in-classes.IgnoreClassesWithAllMemberVariablesBeingPublic
value: '1'
- key: cppcoreguidelines-pro-type-member-init.IgnoreArrays
value: '1'
- key: cppcoreguidelines-pro-type-member-init.UseAssignment
value: '1'
- key: google-readability-function-size.StatementThreshold
value: '800'
- key: google-readability-namespace-comments.ShortNamespaceLines
value: '10'
- key: google-readability-namespace-comments.SpacesBeforeComments
value: '2'
- key: modernize-loop-convert.MaxCopySize
value: '16'
- key: modernize-loop-convert.MinConfidence
value: reasonable
- key: modernize-loop-convert.NamingStyle
value: CamelCase
- key: modernize-pass-by-value.IncludeStyle
value: microsoft
- key: modernize-replace-auto-ptr.IncludeStyle
value: microsoft
- key: modernize-use-bool-literals.IgnoreMacros
value: '0'
- key: modernize-use-default-member-init.IgnoreMacros
value: '0'
- key: modernize-use-default-member-init.UseAssignment
value: '1'
- key: modernize-use-nullptr.NullMacros
value: 'NULL'
- key: readability-braces-around-statements.ShortStatementLines
value: '0'
...

1
.gitignore vendored
View File

@ -1,3 +1,4 @@
.idea/
.cache/
build/
.vs/

View File

@ -1,13 +1,22 @@
# CMakeLists.txt ; Top-level CMake project file.
cmake_minimum_required( VERSION 3.13 )
cmake_minimum_required(VERSION 3.13)
project( Aster VERSION 0.1.0 )
project(Aster VERSION 0.1.0)
set( CMAKE_CXX_STANDARD 23 )
set( CMAKE_CXX_STANDARD_REQUIRED ON )
set( CMAKE_CXX_EXTENSIONS OFF )
set(CMAKE_CXX_STANDARD 20)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_CXX_EXTENSIONS OFF)
set( CMAKE_CXX_FLAGS -Wall )
if (MSVC)
set(CMAKE_CXX_FLAGS "/W4 /GR- /Zi")
add_compile_definitions(_HAS_EXCEPTIONS=1)
add_compile_definitions(_CRT_SECURE_NO_WARNINGS)
else ()
set(CMAKE_CXX_FLAGS "-Wall -g -fno-rtti -fno-exceptions")
endif ()
add_subdirectory( "aster_core" )
include(add_shader.cmake)
add_subdirectory("aster")
add_subdirectory("samples")

View File

@ -12,6 +12,16 @@
"CMAKE_CXX_COMPILER": "/usr/bin/clang++",
"CMAKE_TOOLCHAIN_FILE": "${sourceDir}/vcpkg/scripts/buildsystems/vcpkg.cmake"
}
},
{
"name": "windows",
"generator": "Ninja",
"binaryDir": "${sourceDir}/build",
"cacheVariables": {
"CMAKE_EXPORT_COMPILE_COMMANDS": true,
"CMAKE_MAKE_PROGRAM": "ninja",
"CMAKE_TOOLCHAIN_FILE": "${sourceDir}/vcpkg/scripts/buildsystems/vcpkg.cmake"
}
}
]
}

36
add_shader.cmake Normal file
View File

@ -0,0 +1,36 @@
function(add_shader TARGET SHADER)
find_package(Vulkan REQUIRED COMPONENTS dxc)
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-output-path ${CMAKE_CURRENT_BINARY_DIR}/${SHADER}.spv)
get_filename_component(current-output-dir ${current-output-path} DIRECTORY)
file(MAKE_DIRECTORY ${current-output-dir})
if (Vulkan_dxc_exe_FOUND AND ${shader-ext} STREQUAL ".hlsl")
message("Marked as hlsl file. ${current-output-path}")
add_custom_command(
OUTPUT ${current-output-path}
COMMAND Vulkan::dxc_exe -spirv -T "${shader-type}_6_0" -E main ${current-shader-path} -Fo ${current-output-path}
DEPENDS ${current-shader-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 -o ${current-output-path} ${current-shader-path}
DEPENDS ${current-shader-path}
IMPLICIT_DEPENDS CXX ${current-shader-path}
VERBATIM)
endif ()
# Make sure our build depends on this output.
set_source_files_properties(${current-output-path} PROPERTIES GENERATED TRUE)
target_sources(${TARGET} PRIVATE ${current-output-path})
endfunction(add_shader)

51
aster/CMakeLists.txt Normal file
View File

@ -0,0 +1,51 @@
# CMakeList.txt ; CMake project for Aster Core
cmake_minimum_required(VERSION 3.13)
find_package(glm CONFIG REQUIRED)
find_package(glfw3 CONFIG REQUIRED)
find_path(SCOTTT_DEBUGBREAK_INCLUDE_DIRS "debugbreak.h")
find_package(Vulkan REQUIRED)
find_package(fmt CONFIG REQUIRED)
find_package(VulkanMemoryAllocator CONFIG REQUIRED)
find_package(EASTL CONFIG REQUIRED)
set(HEADER_FILES
constants.h
config.h
logger.h
global.h
context.h
window.h
physical_device.h
device.h
swapchain.h
"pipeline.h"
queue_allocation.h
buffer.h)
set(SOURCE_FILES
logger.cpp
global.cpp
context.cpp
window.cpp
physical_device.cpp
device.cpp
swapchain.cpp
pipeline.cpp
buffer.cpp)
add_library(aster_core STATIC ${SOURCE_FILES} ${HEADER_FILES})
set_property(TARGET aster_core PROPERTY CXX_STANDARD 20)
target_precompile_headers(aster_core PUBLIC global.h)
target_include_directories(aster_core PUBLIC ${CMAKE_CURRENT_SOURCE_DIR})
target_link_libraries(aster_core PUBLIC glm::glm-header-only)
target_link_libraries(aster_core PRIVATE glfw)
target_include_directories(aster_core PRIVATE ${SCOTTT_DEBUGBREAK_INCLUDE_DIRS})
target_link_libraries(aster_core PRIVATE fmt::fmt)
target_link_libraries(aster_core PRIVATE EASTL)
target_link_libraries(aster_core PUBLIC Vulkan::Headers GPUOpen::VulkanMemoryAllocator)

108
aster/buffer.cpp Normal file
View File

@ -0,0 +1,108 @@
// =============================================
// Aster: buffer.cpp
// Copyright (c) 2020-2024 Anish Bhobe
// =============================================
#include "buffer.h"
#include <ranges>
void
Buffer::Destroy(const Device *device)
{
if (!m_Buffer)
return;
vmaDestroyBuffer(device->m_Allocator, m_Buffer, m_Allocation);
m_Buffer = nullptr;
}
void
Buffer::Allocate(const Device *device, usize size, vk::BufferUsageFlags bufferUsage,
VmaAllocationCreateFlags allocationFlags, VmaMemoryUsage memoryUsage, cstr name)
{
assert(size <= SIZE_MASK);
vk::BufferCreateInfo bufferCreateInfo = {
.size = size,
.usage = bufferUsage,
.sharingMode = vk::SharingMode::eExclusive,
};
const VmaAllocationCreateInfo allocationCreateInfo = {
.flags = allocationFlags,
.usage = memoryUsage,
};
VkBuffer buffer;
VmaAllocation allocation;
VmaAllocationInfo allocationInfo;
auto result = Cast<vk::Result>(vmaCreateBuffer(device->m_Allocator, Recast<VkBufferCreateInfo *>(&bufferCreateInfo),
&allocationCreateInfo, &buffer, &allocation, &allocationInfo));
ERROR_IF(Failed(result), "Could not allocate buffer. Cause: {}", result) THEN_ABORT(result);
m_Buffer = buffer;
m_Size = size & SIZE_MASK;
m_Allocation = allocation;
m_Mapped = allocationInfo.pMappedData;
vk::MemoryPropertyFlags memoryPropertyFlags;
vmaGetAllocationMemoryProperties(device->m_Allocator, allocation,
Recast<VkMemoryPropertyFlags *>(&memoryPropertyFlags));
if (memoryPropertyFlags & vk::MemoryPropertyFlagBits::eHostVisible)
{
m_Size |= HOST_ACCESSIBLE_BIT;
}
device->SetName(m_Buffer, name);
}
void
Buffer::Write(const Device *device, usize offset, usize size, void *data)
{
assert(IsHostVisible());
if (!IsMapped())
{
auto result = Cast<vk::Result>(vmaMapMemory(device->m_Allocator, m_Allocation, &m_Mapped));
ERROR_IF(Failed(result), "Memory mapping failed. Cause: {}", result);
if (!Failed(result))
{
memcpy(m_Mapped, data, size);
vmaUnmapMemory(device->m_Allocator, m_Allocation);
m_Mapped = nullptr;
}
}
else
{
memcpy(m_Mapped, data, size);
}
// TODO: Debug this.
// auto result = Cast<vk::Result>(vmaCopyMemoryToAllocation(device->m_Allocator, &data, m_Allocation, 0, 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
VertexBuffer::Init(const Device *device, usize size, cstr name)
{
Allocate(device, size, vk::BufferUsageFlagBits::eVertexBuffer | vk::BufferUsageFlagBits::eTransferDst,
VMA_ALLOCATION_CREATE_DEDICATED_MEMORY_BIT, 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);
}

68
aster/buffer.h Normal file
View File

@ -0,0 +1,68 @@
// =============================================
// Aster: buffer.h
// Copyright (c) 2020-2024 Anish Bhobe
// =============================================
#pragma once
#include "device.h"
#include "global.h"
struct Device;
struct Buffer
{
vk::Buffer m_Buffer = nullptr;
VmaAllocation m_Allocation = nullptr;
void *m_Mapped = nullptr;
[[nodiscard]] usize GetSize() const;
[[nodiscard]] bool IsHostVisible() const;
[[nodiscard]] bool IsMapped() const;
void Destroy(const Device *device);
void Write(const Device *device, usize offset, usize size, void *data);
protected:
void Allocate(const Device *device, usize size, vk::BufferUsageFlags bufferUsage,
VmaAllocationCreateFlags allocationFlags, VmaMemoryUsage memoryUsage, cstr name);
usize m_Size = 0;
constexpr static usize HOST_ACCESSIBLE_BIT = 1llu << 63;
constexpr static usize SIZE_MASK = ~HOST_ACCESSIBLE_BIT;
};
struct UniformBuffer : Buffer
{
void Init(const Device *device, usize size, cstr name = nullptr);
};
struct VertexBuffer : Buffer
{
void Init(const Device *device, usize size, cstr name = nullptr);
void Write(const Device *device, void *data, usize size, usize offset) const = delete;
};
struct StagingBuffer : Buffer
{
void Init(const Device *device, usize size, cstr name = nullptr);
};
inline usize
Buffer::GetSize() const
{
return m_Size & SIZE_MASK;
}
inline bool
Buffer::IsHostVisible() const
{
return m_Size & HOST_ACCESSIBLE_BIT;
}
inline bool
Buffer::IsMapped() const
{
return m_Mapped;
}

View File

@ -11,7 +11,12 @@
#define GLFW_INCLUDE_VULKAN
#define VULKAN_HPP_DISPATCH_LOADER_DYNAMIC 1
#define VULKAN_HPP_NO_STRUCT_CONSTRUCTORS
#define VULKAN_HPP_NO_EXCEPTIONS
#define VULKAN_HPP_DISABLE_ENHANCED_MODE 1
#define VMA_STATIC_VULKAN_FUNCTIONS 0
#define VMA_DYNAMIC_VULKAN_FUNCTIONS 1
#define EASTL_NO_EXCEPTIONS 1
#if defined(NDEBUG)
#define USE_OPTICK (0)

138
aster/constants.h Normal file
View File

@ -0,0 +1,138 @@
// =============================================
// Aster: constants.h
// Copyright (c) 2020-2024 Anish Bhobe
// =============================================
#pragma once
#include <vulkan/vulkan_core.h>
#include <cstdint>
#include <cstdio>
#include <glm/glm.hpp>
using c8 = char;
using u8 = uint8_t;
using u16 = uint16_t;
using u32 = uint32_t;
using u64 = uint64_t;
using i8 = int8_t;
using i16 = int16_t;
using i32 = int32_t;
using i64 = int64_t;
using f32 = float;
using f64 = double;
using f128 = long double;
using b8 = bool;
using b32 = u32;
using usize = size_t;
using p64 = intptr_t;
using cstr = const char *;
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
operator""_deg(long double degrees)
{
return glm::radians<f32>(Cast<f32>(degrees));
}
constexpr f32
operator""_deg(unsigned long long int degrees)
{
return glm::radians<f32>(Cast<f32>(degrees));
}
constexpr f32
operator""_rad(long double rads)
{
return Cast<f32>(rads);
}
constexpr f32
operator""_rad(unsigned long long int rads)
{
return Cast<f32>(rads);
}
using glm::ivec2;
using glm::ivec3;
using glm::ivec4;
using glm::vec2;
using glm::vec3;
using glm::vec4;
using glm::mat2;
using glm::mat3;
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,
};
template <typename T>
constexpr T MaxValue = std::numeric_limits<T>::max();
template <typename T>
constexpr T MinValue = std::numeric_limits<T>::min();
template <typename T>
constexpr T LowestValue = std::numeric_limits<T>::lowest();
template <typename T>
constexpr T ErrEpsilon = std::numeric_limits<T>::epsilon();
template <typename T>
constexpr T PositiveInf = std::numeric_limits<T>::infinity();
template <typename T>
constexpr T NegativeInf = -std::numeric_limits<T>::infinity();
template <typename T>
constexpr T Qnan = std::numeric_limits<T>::quiet_NaN();
template <typename T>
constexpr T Snan = std::numeric_limits<T>::signalling_NaN();

124
aster/context.cpp Normal file
View File

@ -0,0 +1,124 @@
// =============================================
// Aster: context.cpp
// Copyright (c) 2020-2024 Anish Bhobe
// =============================================
#include "context.h"
#include <EASTL/array.h>
#include <EASTL/fixed_vector.h>
constexpr eastl::array VALIDATION_LAYERS = {
"VK_LAYER_KHRONOS_validation",
};
VKAPI_ATTR b32 VKAPI_CALL
DebugCallback(const VkDebugUtilsMessageSeverityFlagBitsEXT messageSeverity,
const VkDebugUtilsMessageTypeFlagsEXT messageType,
const VkDebugUtilsMessengerCallbackDataEXT *callbackData, [[maybe_unused]] void *userData)
{
using Severity = vk::DebugUtilsMessageSeverityFlagsEXT;
using SeverityBits = vk::DebugUtilsMessageSeverityFlagBitsEXT;
using MessageType = vk::DebugUtilsMessageTypeFlagsEXT;
using MessageTypeBits = vk::DebugUtilsMessageTypeFlagBitsEXT;
const auto severity = Severity(messageSeverity);
if (MessageType(messageType) & MessageTypeBits::eValidation)
{
if (severity & SeverityBits::eError)
ERROR("{}", callbackData->pMessage);
if (severity & SeverityBits::eWarning)
WARN("{}", callbackData->pMessage);
if (severity & SeverityBits::eInfo)
INFO("{}", callbackData->pMessage);
if (severity & SeverityBits::eVerbose)
VERBOSE("{}", callbackData->pMessage);
}
return false;
}
Context::Context(const cstr appName, const Version version, bool enableValidation)
{
INFO_IF(enableValidation, "Validation Layers enabled");
if (!glfwInit())
{
const char *error = nullptr;
const auto code = glfwGetError(&error);
ERROR("GLFW Init failed. Cause: ({}) {}", code, error)
THEN_ABORT(code);
}
// TODO Get/Check API Version
// Creating Instance
const vk::ApplicationInfo appInfo = {
.pApplicationName = appName,
.applicationVersion = version.GetVkVersion(),
.pEngineName = PROJECT_NAME,
.engineVersion = version.GetVkVersion(),
.apiVersion = ASTER_API_VERSION,
};
const vk::DebugUtilsMessengerCreateInfoEXT debugUtilsMessengerCreateInfo = {
.messageSeverity = vk::DebugUtilsMessageSeverityFlagBitsEXT::eError |
vk::DebugUtilsMessageSeverityFlagBitsEXT::eWarning |
vk::DebugUtilsMessageSeverityFlagBitsEXT::eInfo,
.messageType = vk::DebugUtilsMessageTypeFlagBitsEXT::eGeneral |
vk::DebugUtilsMessageTypeFlagBitsEXT::ePerformance |
vk::DebugUtilsMessageTypeFlagBitsEXT::eValidation,
.pfnUserCallback = DebugCallback,
.pUserData = nullptr,
};
u32 glfwExtensionCount = 0;
cstr *glfwExtensions = glfwGetRequiredInstanceExtensions(&glfwExtensionCount);
eastl::fixed_vector<cstr, 3> instanceExtensions(glfwExtensions, glfwExtensions + glfwExtensionCount);
if (enableValidation)
{
instanceExtensions.push_back(VK_EXT_DEBUG_UTILS_EXTENSION_NAME);
}
const vk::DynamicLoader dl;
// ReSharper disable once CppInconsistentNaming
const auto vkGetInstanceProcAddr = dl.getProcAddress<PFN_vkGetInstanceProcAddr>("vkGetInstanceProcAddr");
VULKAN_HPP_DEFAULT_DISPATCHER.init(vkGetInstanceProcAddr);
const auto instanceCreateInfo = vk::InstanceCreateInfo{
.pNext = enableValidation ? &debugUtilsMessengerCreateInfo : nullptr,
.pApplicationInfo = &appInfo,
.enabledLayerCount = enableValidation ? Cast<u32>(VALIDATION_LAYERS.size()) : 0,
.ppEnabledLayerNames = enableValidation ? VALIDATION_LAYERS.data() : nullptr,
.enabledExtensionCount = Cast<u32>(instanceExtensions.size()),
.ppEnabledExtensionNames = instanceExtensions.data(),
};
// May throw. Irrecoverable.
vk::Result result = createInstance(&instanceCreateInfo, nullptr, &m_Instance);
ERROR_IF(result, "Instance creation failed. Cause: {}", result)
THEN_ABORT(result)
ELSE_DEBUG("Instance Created.");
VULKAN_HPP_DEFAULT_DISPATCHER.init(m_Instance);
// Debug Messenger
if (enableValidation)
{
result = m_Instance.createDebugUtilsMessengerEXT(&debugUtilsMessengerCreateInfo, nullptr, &m_DebugMessenger);
ERROR_IF(result, "Debug Messenger creation failed. Cause: {}", result) // Non-critical. Continue.
ELSE_DEBUG("Debug Messenger Created.");
}
}
Context::~Context()
{
if (m_DebugMessenger)
{
m_Instance.destroy(m_DebugMessenger, nullptr);
DEBUG("Debug Messenger destroyed");
}
m_Instance.destroy(nullptr);
DEBUG("Instance destroyed");
glfwTerminate();
}

26
aster/context.h Normal file
View File

@ -0,0 +1,26 @@
// =============================================
// Aster: context.h
// Copyright (c) 2020-2024 Anish Bhobe
// =============================================
#pragma once
#include "global.h"
/**
* @class Context
*
* @brief Vulkan context to handle device initialization logic.
*
* Handles the required hardware interactions.
*/
struct Context final
{
// Members
vk::Instance m_Instance = nullptr;
vk::DebugUtilsMessengerEXT m_DebugMessenger = nullptr;
// Ctor/Dtor
Context(cstr appName, Version version, bool enableValidation = true);
~Context();
};

111
aster/device.cpp Normal file
View File

@ -0,0 +1,111 @@
// =============================================
// Aster: device.cpp
// Copyright (c) 2020-2024 Anish Bhobe
// =============================================
#include "device.h"
#include "context.h"
#include "physical_device.h"
#include "queue_allocation.h"
#include <EASTL/array.h>
#include <EASTL/fixed_vector.h>
// TODO: This will need to be flexible for devices that don't support some of the extensions.
constexpr eastl::array DEVICE_EXTENSIONS = {
VK_KHR_SWAPCHAIN_EXTENSION_NAME,
};
Device::Device(const Context *context, PhysicalDevice *physicalDevice, Features *enabledFeatures,
const eastl::vector<QueueAllocation> &queueAllocations, NameString &&name)
: m_Name(std::move(name))
, m_PhysicalDevice(physicalDevice->m_PhysicalDevice)
{
// Shouldn't have more than 4 deviceQueueFamilies in use anyway. Else we can heap
eastl::fixed_vector<vk::DeviceQueueCreateInfo, 4> deviceQueueCreateInfos;
deviceQueueCreateInfos.reserve(queueAllocations.size());
u32 numPriorities = 0;
for (auto [_, count] : queueAllocations)
{
numPriorities = std::max(count, numPriorities);
}
// Shouldn't have more than 4 queues either.
eastl::fixed_vector<f32, 4> priorities(numPriorities, 1.0f);
for (auto [family, count] : queueAllocations)
{
deviceQueueCreateInfos.push_back({
.queueFamilyIndex = family,
.queueCount = count,
.pQueuePriorities = priorities.data(),
});
}
vk::PhysicalDeviceFeatures *deviceFeatures = &enabledFeatures->m_Vulkan10Features;
vk::PhysicalDeviceVulkan11Features *vulkan11Features = &enabledFeatures->m_Vulkan11Features;
vk::PhysicalDeviceVulkan12Features *vulkan12Features = &enabledFeatures->m_Vulkan12Features;
vk::PhysicalDeviceVulkan13Features *vulkan13Features = &enabledFeatures->m_Vulkan13Features;
vulkan11Features->pNext = vulkan12Features;
vulkan12Features->pNext = vulkan13Features;
vk::DeviceCreateInfo deviceCreateInfo = {
.pNext = vulkan11Features,
.queueCreateInfoCount = Cast<u32>(deviceQueueCreateInfos.size()),
.pQueueCreateInfos = deviceQueueCreateInfos.data(),
.enabledExtensionCount = Cast<u32>(DEVICE_EXTENSIONS.size()),
.ppEnabledExtensionNames = DEVICE_EXTENSIONS.data(),
.pEnabledFeatures = deviceFeatures,
};
vk::Result result = m_PhysicalDevice.createDevice(&deviceCreateInfo, nullptr, &m_Device);
ERROR_IF(Failed(result), "Could not initialize Vulkan Device. Cause: {}", result)
THEN_ABORT(result)
ELSE_DEBUG("{} ({}) Initialized.", m_Name, physicalDevice->m_DeviceProperties.deviceName.data());
SetName(m_Device, m_Name.data());
VmaVulkanFunctions vmaVulkanFunctions = {
.vkGetInstanceProcAddr = vk::defaultDispatchLoaderDynamic.vkGetInstanceProcAddr,
.vkGetDeviceProcAddr = vk::defaultDispatchLoaderDynamic.vkGetDeviceProcAddr,
};
const VmaAllocatorCreateInfo allocatorCreateInfo = {
.physicalDevice = m_PhysicalDevice,
.device = m_Device,
.pVulkanFunctions = &vmaVulkanFunctions,
.instance = context->m_Instance,
.vulkanApiVersion = ASTER_API_VERSION,
};
result = Cast<vk::Result>(vmaCreateAllocator(&allocatorCreateInfo, &m_Allocator));
ERROR_IF(Failed(result), "Memory allocator creation failed. Cause: {}", result)
DO(m_Device.destroy(nullptr))
THEN_ABORT(result)
ELSE_VERBOSE("Memory Allocator Created");
DEBUG("Created '{}' Successfully", m_Name);
}
Device::~Device()
{
if (m_Allocator)
{
vmaDestroyAllocator(m_Allocator);
m_Allocator = nullptr;
DEBUG("Memory Allocator Destroyed");
}
m_Device.destroy(nullptr);
DEBUG("Device '{}' Destroyed", m_Name);
m_PhysicalDevice = nullptr;
}
vk::Queue
Device::GetQueue(const u32 familyIndex, const u32 queueIndex) const
{
vk::Queue queue;
m_Device.getQueue(familyIndex, queueIndex, &queue);
return queue;
}

55
aster/device.h Normal file
View File

@ -0,0 +1,55 @@
// =============================================
// Aster: device.h
// Copyright (c) 2020-2024 Anish Bhobe
// =============================================
#pragma once
#include "global.h"
#include <EASTL/vector.h>
struct QueueAllocation;
struct Context;
struct PhysicalDevice;
struct Features
{
vk::PhysicalDeviceFeatures m_Vulkan10Features;
vk::PhysicalDeviceVulkan11Features m_Vulkan11Features;
vk::PhysicalDeviceVulkan12Features m_Vulkan12Features;
vk::PhysicalDeviceVulkan13Features m_Vulkan13Features;
};
struct Device final
{
NameString m_Name;
vk::PhysicalDevice m_PhysicalDevice = nullptr;
vk::Device m_Device = nullptr;
VmaAllocator m_Allocator = nullptr;
Device(const Context *context, PhysicalDevice *physicalDevice, Features *enabledFeatures,
const eastl::vector<QueueAllocation> &queueAllocations, NameString &&name);
~Device();
template <typename T>
requires vk::isVulkanHandleType<T>::value void SetName(const T &object, cstr name) const;
[[nodiscard]] vk::Queue GetQueue(u32 familyIndex, u32 queueIndex) const;
};
template <typename T>
requires vk::isVulkanHandleType<T>::value void
Device::SetName(const T &object, cstr name) const
{
if (!name)
return;
auto handle = Recast<u64>(Cast<typename T::NativeType>(object));
const vk::DebugUtilsObjectNameInfoEXT objectNameInfo = {
.objectType = object.objectType,
.objectHandle = handle,
.pObjectName = name,
};
vk::Result result = m_Device.setDebugUtilsObjectNameEXT(&objectNameInfo);
WARN_IF(Failed(result), "Could not name {:x}: {} as {}. Cause: {}", handle, to_string(object.objectType), name,
result);
}

29
aster/global.cpp Normal file
View File

@ -0,0 +1,29 @@
// =============================================
// Aster: global.cpp
// Copyright (c) 2020-2024 Anish Bhobe
// =============================================
#include "global.h"
#include <cstdarg>
#include <cstdio>
#define VMA_IMPLEMENTATION
#include <vk_mem_alloc.h>
// NOTE: Vulkan Dispatch Loader Storage - Should only appear once.
VULKAN_HPP_DEFAULT_DISPATCH_LOADER_DYNAMIC_STORAGE
void *
operator new[](size_t size, const char * /*pName*/, int /*flags*/, unsigned /*debugFlags*/, const char * /*file*/,
int /*line*/)
{
return new u8[size];
}
void *
operator new[](size_t size, size_t /*alignment*/, size_t /*alignmentOffset*/, const char * /*pName*/, int /*flags*/,
unsigned /*debugFlags*/, const char * /*file*/, int /*line*/)
{
return new u8[size];
}

124
aster/global.h Normal file
View File

@ -0,0 +1,124 @@
// =============================================
// Aster: global.h
// Copyright (c) 2020-2024 Anish Bhobe
// =============================================
#pragma once
#include "config.h"
#include "constants.h"
#include "logger.h"
#include <GLFW/glfw3.h>
#include <glm/glm.hpp>
#include <fmt/format.h>
// Macros that can collide with functions.
#if defined(max)
#undef max
#endif
#if defined(min)
#undef min
#endif
#define VULKAN_HPP_ASSERT(expr) DEBUG_IF(!(expr), "Vulkan assert failed")
#include <EASTL/fixed_string.h>
#include <EASTL/string.h>
#include <vk_mem_alloc.h>
#include <vulkan/vulkan.hpp>
constexpr u32 ASTER_API_VERSION = VK_API_VERSION_1_3;
#define CODE_LOC " @ " __FILE__ ":" VULKAN_HPP_STRINGIFY(__LINE__)
#define FORCE_TODO static_assert(false)
[[nodiscard]] inline bool
Failed(const vk::Result result)
{
return result != vk::Result::eSuccess;
}
using NameString = eastl::fixed_string<char, 32, false>;
template <typename TFlagBits>
struct std::hash<vk::Flags<TFlagBits>> // NOLINT(*-dcl58-cpp)
{
[[nodiscard]] usize
operator()(const vk::Flags<TFlagBits> &val)
{
return std::hash<u32>()(Cast<u32>(val));
}
};
template <typename T>
[[nodiscard]] usize
HashAny(const T &val)
{
return std::hash<std::remove_cvref_t<T>>()(val);
}
[[nodiscard]] inline usize
HashCombine(const usize hash0, const usize hash1)
{
constexpr usize saltValue = 0x9e3779b9;
const usize tempVar = hash1 + saltValue + (hash0 << 6) + (hash0 >> 2);
return hash0 ^ tempVar;
}
struct Time
{
static constexpr f64 cMaxDelta = 0.1;
inline static f64 m_Elapsed{Qnan<f64>};
inline static f64 m_Delta{Qnan<f64>};
static void
Init()
{
WARN_IF(!std::isnan(m_Elapsed), "Time already init.");
m_Elapsed = glfwGetTime();
m_Delta = 1.0 / 60.0;
}
static void
Update()
{
ERROR_IF(std::isnan(m_Elapsed), "Time not init.");
const auto newElapsed = glfwGetTime();
m_Delta = std::clamp(newElapsed - m_Elapsed, 0.0, cMaxDelta);
m_Elapsed = newElapsed;
}
};
[[nodiscard]] inline usize
ClosestMultiple(const usize val, const usize of)
{
return of * ((val + of - 1) / of);
}
template <>
struct fmt::formatter<vk::Result> : nested_formatter<std::string>
{
auto
// ReSharper disable once CppInconsistentNaming
format(vk::Result result, format_context &ctx) const
{
return write_padded(ctx,
[this, result](auto out) { return v10::format_to(out, "{}", nested(to_string(result))); });
}
};
template <typename TType, usize TCount, bool TOverflow>
struct fmt::formatter<eastl::fixed_string<TType, TCount, TOverflow>> : nested_formatter<cstr>
{
auto
// ReSharper disable once CppInconsistentNaming
format(const eastl::fixed_string<TType, TCount, TOverflow> &str, format_context &ctx) const
{
return write_padded(ctx, [this, str](auto out) { return v10::format_to(out, "{}", nested(str.c_str())); });
}
};

View File

@ -5,13 +5,16 @@
#include "logger.h"
Logger g_logger = Logger();
Logger g_Logger = Logger();
// ReSharper disable once CppInconsistentNaming
/* Credits to Const-me */
//namespace eastl {
// void __cdecl AssertionFailure(const char* af)
// {
// ERROR(af);
// __debugbreak();
// }
//}
namespace eastl
{
void
AssertionFailure(const char *af)
{
ERROR("{}", af);
debug_break();
}
} // namespace eastl

206
aster/logger.h Normal file
View File

@ -0,0 +1,206 @@
// =============================================
// Aster: logger.h
// Copyright (c) 2020-2024 Anish Bhobe
// =============================================
#pragma once
#include "constants.h"
#include <debugbreak.h>
#include <fmt/core.h>
struct Logger
{
enum class LogType : u32
{
eError,
eWarning,
eInfo,
eDebug,
eVerbose,
};
u32 m_MinimumLoggingLevel{Cast<u32>(LogType::eDebug)};
void
SetMinimumLoggingLevel(LogType logType)
{
m_MinimumLoggingLevel = Cast<u32>(logType);
}
template <LogType TLogLevel>
constexpr static const char *
ToCstr()
{
if constexpr (TLogLevel == LogType::eError)
return "[ERROR]:";
if constexpr (TLogLevel == LogType::eWarning)
return "[WARN]: ";
if constexpr (TLogLevel == LogType::eInfo)
return "[INFO]: ";
if constexpr (TLogLevel == LogType::eDebug)
return "[DEBUG]:";
if constexpr (TLogLevel == LogType::eVerbose)
return "[VERB]: ";
}
template <LogType TLogLevel>
constexpr static const char *
ToColorCstr()
{
if constexpr (TLogLevel == LogType::eError)
return ansi_color::Red;
if constexpr (TLogLevel == LogType::eWarning)
return ansi_color::Yellow;
if constexpr (TLogLevel == LogType::eInfo)
return ansi_color::Green;
if constexpr (TLogLevel == LogType::eDebug)
return ansi_color::White;
if constexpr (TLogLevel == LogType::eVerbose)
return ansi_color::Blue;
}
template <LogType TLogLevel>
void
Log(const std::string_view &message, const char *loc, u32 line) const
{
if (Cast<u32>(TLogLevel) <= m_MinimumLoggingLevel)
{
fmt::println("{}{} {} {} at {}:{}{}", ToColorCstr<TLogLevel>(), ToCstr<TLogLevel>(), message.data(),
ansi_color::Black, loc, line, ansi_color::Reset);
}
#if !defined(NDEBUG)
if constexpr (TLogLevel == LogType::eError)
{
debug_break();
}
#endif // !defined(NDEBUG)
}
template <LogType TLogLevel>
void
LogCond(const char *exprStr, const std::string_view &message, const char *loc, u32 line) const
{
if (Cast<u32>(TLogLevel) <= m_MinimumLoggingLevel)
{
fmt::println("{}{} ({}) {} {} at {}:{}{}", ToColorCstr<TLogLevel>(), ToCstr<TLogLevel>(), exprStr,
message.data(), ansi_color::Black, loc, line, ansi_color::Reset);
}
#if !defined(NDEBUG)
if constexpr (TLogLevel == LogType::eError)
{
debug_break();
}
#endif // !defined(NDEBUG)
}
};
extern Logger g_Logger;
#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 WARN(...) g_Logger.Log<Logger::LogType::eWarning>(fmt::format(__VA_ARGS__), __FILE__, __LINE__)
#define INFO(...) g_Logger.Log<Logger::LogType::eInfo>(fmt::format(__VA_ARGS__), __FILE__, __LINE__)
#define ERROR_IF(expr, ...) \
if (Cast<bool>(expr)) [[unlikely]] \
g_Logger.LogCond<Logger::LogType::eError>(#expr, fmt::format(__VA_ARGS__), __FILE__, __LINE__)
#define WARN_IF(expr, ...) \
if (Cast<bool>(expr)) [[unlikely]] \
g_Logger.LogCond<Logger::LogType::eWarning>(#expr, fmt::format(__VA_ARGS__), __FILE__, __LINE__)
#define INFO_IF(expr, ...) \
if (Cast<bool>(expr)) \
g_Logger.LogCond<Logger::LogType::eInfo>(#expr, fmt::format(__VA_ARGS__), __FILE__, __LINE__)
#define ELSE_IF_ERROR(expr, ...) \
; \
else if (Cast<bool>(expr)) \
[[unlikely]] g_Logger.LogCond<Logger::LogType::eError>(#expr, fmt::format(__VA_ARGS__), __FILE__, __LINE__)
#define ELSE_IF_WARN(expr, ...) \
; \
else if (Cast<bool>(expr)) \
[[unlikely]] g_Logger.LogCond<Logger::LogType::eWarning>(#expr, fmt::format(__VA_ARGS__), __FILE__, __LINE__)
#define ELSE_IF_INFO(expr, ...) \
; \
else if (Cast<bool>(expr)) \
g_Logger.LogCond<Logger::LogType::eInfo>(#expr, fmt::format(__VA_ARGS__), __FILE__, __LINE__)
#define ELSE_ERROR(...) \
; \
else [[unlikely]] g_Logger.Log<Logger::LogType::eError>(fmt::format(__VA_ARGS__), __FILE__, __LINE__)
#define ELSE_WARN(...) \
; \
else [[unlikely]] g_Logger.Log<Logger::LogType::eWarning>(fmt::format(__VA_ARGS__), __FILE__, __LINE__)
#define ELSE_INFO(...) \
; \
else g_Logger.Log<Logger::LogType::eInfo>(fmt::format(__VA_ARGS__), __FILE__, __LINE__)
#if !defined(DEBUG_LOG_DISABLED) && !defined(NDEBUG)
#define DEBUG(...) g_Logger.Log<Logger::LogType::eDebug>(fmt::format(__VA_ARGS__), __FILE__, __LINE__)
#define DEBUG_IF(expr, ...) \
if (Cast<bool>(expr)) \
g_Logger.LogCond<Logger::LogType::eDebug>(#expr, fmt::format(__VA_ARGS__), __FILE__, __LINE__)
#define ELSE_IF_DEBUG(expr, ...) \
; \
else if (Cast<bool>(expr)) \
g_Logger.LogCond<Logger::LogType::eDebug>(#expr, fmt::format(__VA_ARGS__), __FILE__, __LINE__)
#define ELSE_DEBUG(...) \
; \
else g_Logger.Log<Logger::LogType::eDebug>(fmt::format(__VA_ARGS__), __FILE__, __LINE__)
#else // !defined(DEBUG_LOG_DISABLED)
#define DEBUG(msg) \
{ \
}
#define DEBUG_IF(expr, msg) \
if (expr) \
(void)msg
#define ELSE_IF_DEBUG(expr, msg) \
; \
if (expr) \
(void)msg
#define ELSE_DEBUG(msg) \
; \
{ \
}
#endif // !defined(DEBUG_LOG_DISABLED)
#if !defined(VERBOSE_LOG_DISABLED) && !defined(NDEBUG)
#define VERBOSE(...) g_Logger.Log<Logger::LogType::eVerbose>(fmt::format(__VA_ARGS__), __FILE__, __LINE__)
#define VERBOSE_IF(expr, ...) \
if (Cast<bool>(expr)) \
g_Logger.LogCond<Logger::LogType::eVerbose>(#expr, fmt::format(__VA_ARGS__), __FILE__, __LINE__)
#define ELSE_IF_VERBOSE(expr, ...) \
; \
else if (Cast<bool>(expr)) \
g_Logger.LogCond<Logger::LogType::eVerbose>(#expr, fmt::format(__VA_ARGS__), __FILE__, __LINE__)
#define ELSE_VERBOSE(...) \
; \
else g_Logger.Log<Logger::LogType::eVerbose>(fmt::format(__VA_ARGS__), __FILE__, __LINE__)
#else // !defined(DEBUG_LOG_DISABLED)
#define VERBOSE(msg) \
{ \
}
#define VERBOSE_IF(expr, msg) \
if (expr) \
(void)msg
#define ELSE_IF_VERBOSE(expr, msg) \
; \
if (expr) \
(void)msg
#define ELSE_VERBOSE(msg) \
; \
{ \
}
#endif // !defined(VERBOSE_LOG_DISABLED)
#define DO(code) , code
#define ABORT(code) exit(Cast<i32>(code))
#define THEN_ABORT(code) , ABORT(code)

165
aster/physical_device.cpp Normal file
View File

@ -0,0 +1,165 @@
// =============================================
// Aster: physical_device.cpp
// Copyright (c) 2020-2024 Anish Bhobe
// =============================================
#include "physical_device.h"
#include "context.h"
#include "window.h"
[[nodiscard]] vk::SurfaceCapabilitiesKHR
GetSurfaceCapabilities(const vk::PhysicalDevice physicalDevice, const vk::SurfaceKHR surface)
{
vk::SurfaceCapabilitiesKHR surfaceCapabilities;
vk::Result result = physicalDevice.getSurfaceCapabilitiesKHR(surface, &surfaceCapabilities);
ERROR_IF(Failed(result), "Fetching surface capabilities failed. Cause: {}", result)
THEN_ABORT(result);
return surfaceCapabilities;
}
[[nodiscard]] eastl::vector<vk::SurfaceFormatKHR>
GetSurfaceFormats(const vk::PhysicalDevice physicalDevice, const vk::SurfaceKHR surface)
{
// vk::Result::eIncomplete should not occur in this function. The rest are errors. Thus, abort is allowed.
u32 count = 0;
vk::Result result = physicalDevice.getSurfaceFormatsKHR(surface, &count, nullptr);
ERROR_IF(Failed(result), "Could not get surface formats. Cause: {}", result)
THEN_ABORT(result);
eastl::vector<vk::SurfaceFormatKHR> surfaceFormats(count);
result = physicalDevice.getSurfaceFormatsKHR(surface, &count, surfaceFormats.data());
ERROR_IF(Failed(result), "Could not get surface formats. Cause: {}", result)
THEN_ABORT(result);
return surfaceFormats;
}
[[nodiscard]] eastl::vector<vk::PresentModeKHR>
GetSurfacePresentModes(const vk::PhysicalDevice physicalDevice, const vk::SurfaceKHR surface)
{
// vk::Result::eIncomplete should not occur in this function. The rest are errors. Thus, abort is allowed.
u32 count = 0;
vk::Result result = physicalDevice.getSurfacePresentModesKHR(surface, &count, nullptr);
ERROR_IF(Failed(result), "Could not get present modes. Cause: {}", result)
THEN_ABORT(result);
eastl::vector<vk::PresentModeKHR> presentModes(count);
result = physicalDevice.getSurfacePresentModesKHR(surface, &count, presentModes.data());
ERROR_IF(Failed(result), "Could not get present modes. Cause: {}", result)
THEN_ABORT(result);
return presentModes;
}
[[nodiscard]] bool
GetQueuePresentSupport(const u32 queueFamilyIndex, const vk::SurfaceKHR surface,
const vk::PhysicalDevice physicalDevice)
{
b32 supported = false;
const vk::Result result = physicalDevice.getSurfaceSupportKHR(queueFamilyIndex, surface, &supported);
ERROR_IF(Failed(result), "Could not get queue family surface support. Cause: {}", result)
THEN_ABORT(result);
return supported;
}
[[nodiscard]] eastl::fixed_vector<vk::QueueFamilyProperties, 32>
GetQueueFamilyProperties(const vk::PhysicalDevice physicalDevice)
{
// Devices rarely have more than 32 queue families. Thus fixed vector
u32 count = 0;
physicalDevice.getQueueFamilyProperties(&count, nullptr);
eastl::fixed_vector<vk::QueueFamilyProperties, 32> queueFamilyProperties(count);
physicalDevice.getQueueFamilyProperties(&count, queueFamilyProperties.data());
return queueFamilyProperties;
}
// Size 384 return.
[[nodiscard]] eastl::vector<QueueFamilyInfo>
GetQueueFamilies(const vk::SurfaceKHR surface, const vk::PhysicalDevice physicalDevice)
{
auto queueFamilyProperties = GetQueueFamilyProperties(physicalDevice);
eastl::vector<QueueFamilyInfo> queueFamilyInfos;
queueFamilyInfos.reserve(queueFamilyProperties.size());
u32 familyIndex = 0;
for (auto qfp : queueFamilyProperties)
{
QueueSupportFlags support = {};
if (qfp.queueFlags | vk::QueueFlagBits::eGraphics)
{
support |= QueueSupportFlagBits::eGraphics;
}
if (qfp.queueFlags | vk::QueueFlagBits::eTransfer)
{
support |= QueueSupportFlagBits::eTransfer;
}
if (qfp.queueFlags | vk::QueueFlagBits::eCompute)
{
support |= QueueSupportFlagBits::eCompute;
}
if (GetQueuePresentSupport(familyIndex, surface, physicalDevice))
{
support |= QueueSupportFlagBits::ePresent;
}
queueFamilyInfos.push_back({
.m_Index = familyIndex,
.m_Count = qfp.queueCount,
.m_Support = support,
});
familyIndex++;
}
return queueFamilyInfos;
}
PhysicalDevice::PhysicalDevice(const vk::SurfaceKHR surface, const vk::PhysicalDevice physicalDevice)
{
physicalDevice.getProperties(&m_DeviceProperties);
physicalDevice.getFeatures(&m_DeviceFeatures);
m_SurfaceFormats = GetSurfaceFormats(physicalDevice, surface);
m_PresentModes = GetSurfacePresentModes(physicalDevice, surface);
m_QueueFamilies = GetQueueFamilies(surface, physicalDevice);
m_PhysicalDevice = physicalDevice;
}
eastl::fixed_vector<vk::PhysicalDevice, 8>
EnumeratePhysicalDevices(const vk::Instance instance)
{
u32 count = 0;
vk::Result result = instance.enumeratePhysicalDevices(&count, nullptr);
ERROR_IF(Failed(result), "Could not fetch vulkan devices. Cause: {}", result)
THEN_ABORT(result);
eastl::fixed_vector<vk::PhysicalDevice, 8> physicalDevices(count);
result = instance.enumeratePhysicalDevices(&count, physicalDevices.data());
ERROR_IF(Failed(result), "Could not fetch vulkan devices. Cause: {}", result)
THEN_ABORT(result);
return physicalDevices;
}
PhysicalDevices::PhysicalDevices(const Window *window, const Context *context)
{
auto surface = window->m_Surface;
auto physicalDevices = EnumeratePhysicalDevices(context->m_Instance);
for (auto physicalDevice : physicalDevices)
{
this->emplace_back(surface, physicalDevice);
}
}

56
aster/physical_device.h Normal file
View File

@ -0,0 +1,56 @@
// =============================================
// Aster: physical_device.h
// Copyright (c) 2020-2024 Anish Bhobe
// =============================================
#pragma once
#include "global.h"
#include <EASTL/fixed_vector.h>
struct Window;
struct Context;
enum class QueueSupportFlagBits
{
eGraphics = 0b0001,
eTransfer = 0b0010,
eCompute = 0b0100,
ePresent = 0b1000,
};
using QueueSupportFlags = vk::Flags<QueueSupportFlagBits>;
struct QueueFamilyInfo
{
u32 m_Index;
u32 m_Count;
QueueSupportFlags m_Support;
};
[[nodiscard]] vk::SurfaceCapabilitiesKHR
GetSurfaceCapabilities(vk::PhysicalDevice physicalDevice, vk::SurfaceKHR surface);
[[nodiscard]] eastl::vector<vk::SurfaceFormatKHR>
GetSurfaceFormats(vk::PhysicalDevice physicalDevice, vk::SurfaceKHR surface);
[[nodiscard]] eastl::vector<vk::PresentModeKHR>
GetSurfacePresentModes(vk::PhysicalDevice physicalDevice, vk::SurfaceKHR surface);
struct PhysicalDevice
{
vk::PhysicalDevice m_PhysicalDevice;
vk::PhysicalDeviceProperties m_DeviceProperties;
vk::PhysicalDeviceFeatures m_DeviceFeatures;
eastl::vector<vk::SurfaceFormatKHR> m_SurfaceFormats;
eastl::vector<vk::PresentModeKHR> m_PresentModes;
eastl::vector<QueueFamilyInfo> m_QueueFamilies;
PhysicalDevice(vk::SurfaceKHR surface, vk::PhysicalDevice physicalDevice);
};
class PhysicalDevices : public eastl::fixed_vector<PhysicalDevice, 4>
{
public:
PhysicalDevices(const Window *window, const Context *context);
};

21
aster/pipeline.cpp Normal file
View File

@ -0,0 +1,21 @@
// =============================================
// Aster: pipeline.cpp
// Copyright (c) 2020-2024 Anish Bhobe
// =============================================
#include "pipeline.h"
#include "device.h"
Pipeline::Pipeline(const Device *device, vk::PipelineLayout layout, vk::Pipeline pipeline)
: m_Device(device)
, m_Layout(layout)
, m_Pipeline(pipeline)
{
}
Pipeline::~Pipeline()
{
m_Device->m_Device.destroy(m_Pipeline, nullptr);
m_Device->m_Device.destroy(m_Layout, nullptr);
}

20
aster/pipeline.h Normal file
View File

@ -0,0 +1,20 @@
// =============================================
// Aster: pipeline.h
// Copyright (c) 2020-2024 Anish Bhobe
// =============================================
#pragma once
#include "global.h"
struct Device;
struct Pipeline
{
const Device *m_Device;
vk::PipelineLayout m_Layout;
vk::Pipeline m_Pipeline;
Pipeline(const Device *device, vk::PipelineLayout layout, vk::Pipeline pipeline);
~Pipeline();
};

14
aster/queue_allocation.h Normal file
View File

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

171
aster/swapchain.cpp Normal file
View File

@ -0,0 +1,171 @@
/// =============================================
// Aster: swapchain.cpp
// Copyright (c) 2020-2024 Anish Bhobe
// ==============================================
#include "swapchain.h"
#include "device.h"
#include "physical_device.h"
#include "window.h"
Swapchain::Swapchain(const Window *window, const Device *device, NameString &&name)
: m_Device(device)
, m_Name(std::move(name))
{
this->Create(window);
}
Swapchain::~Swapchain()
{
this->Cleanup();
}
void
Swapchain::Create(const Window *window)
{
auto surfaceCapabilities = GetSurfaceCapabilities(m_Device->m_PhysicalDevice, window->m_Surface);
auto surfaceFormats = GetSurfaceFormats(m_Device->m_PhysicalDevice, window->m_Surface);
auto presentModes = GetSurfacePresentModes(m_Device->m_PhysicalDevice, window->m_Surface);
m_Format = vk::Format::eUndefined;
vk::ColorSpaceKHR swapchainColorSpace = vk::ColorSpaceKHR::eSrgbNonlinear;
for (auto [format, colorSpace] : surfaceFormats)
{
if (format == vk::Format::eB8G8R8A8Srgb && colorSpace == vk::ColorSpaceKHR::eSrgbNonlinear)
{
m_Format = format;
swapchainColorSpace = colorSpace;
}
}
if (m_Format == vk::Format::eUndefined)
{
WARN("Preferred Swapchain format not found. Falling back.");
auto [format, colorSpace] = surfaceFormats.front();
m_Format = format;
swapchainColorSpace = colorSpace;
}
vk::PresentModeKHR swapchainPresentMode = vk::PresentModeKHR::eFifo;
for (const auto presentMode : presentModes)
{
if (presentMode == vk::PresentModeKHR::eMailbox)
{
swapchainPresentMode = presentMode;
break;
}
}
if (surfaceCapabilities.currentExtent.width != MaxValue<u32>)
{
m_Extent = surfaceCapabilities.currentExtent;
}
else
{
auto [width, height] = window->GetSize();
auto [minWidth, minHeight] = surfaceCapabilities.minImageExtent;
auto [maxWidth, maxHeight] = surfaceCapabilities.maxImageExtent;
m_Extent.width = glm::clamp(width, minWidth, maxWidth);
m_Extent.height = glm::clamp(height, minHeight, maxHeight);
}
u32 swapchainImageCount = 3;
if (surfaceCapabilities.maxImageCount > 0)
{
swapchainImageCount =
glm::clamp(swapchainImageCount, surfaceCapabilities.minImageCount, surfaceCapabilities.maxImageCount);
}
// TODO: Note that different queues might need the images to be shared.
const vk::SwapchainCreateInfoKHR swapchainCreateInfo = {
.surface = window->m_Surface,
.minImageCount = swapchainImageCount,
.imageFormat = m_Format,
.imageColorSpace = swapchainColorSpace,
.imageExtent = m_Extent,
.imageArrayLayers = 1,
.imageUsage = vk::ImageUsageFlagBits::eColorAttachment,
.imageSharingMode = vk::SharingMode::eExclusive,
.preTransform = surfaceCapabilities.currentTransform,
.compositeAlpha = vk::CompositeAlphaFlagBitsKHR::eOpaque,
.presentMode = swapchainPresentMode,
.clipped = true,
.oldSwapchain = m_Swapchain,
};
vk::Device device = m_Device->m_Device;
vk::SwapchainKHR swapchain;
vk::Result result = device.createSwapchainKHR(&swapchainCreateInfo, nullptr, &swapchain);
ERROR_IF(Failed(result), "Swapchain {} creation failed. Cause {}", m_Name, result)
THEN_ABORT(result)
ELSE_DEBUG("Created Swapchain '{}'", m_Name);
// Irrelevant on the first run. Required for re-creation.
Cleanup();
m_Swapchain = swapchain;
m_Device->SetName(m_Swapchain, m_Name.data());
result = device.getSwapchainImagesKHR(m_Swapchain, &swapchainImageCount, nullptr);
ERROR_IF(Failed(result), "Failed getting swapchain {}'s images. Cause {}", m_Name, result)
THEN_ABORT(result);
// Managed by the Swapchain.
m_Images.resize(swapchainImageCount);
result = device.getSwapchainImagesKHR(m_Swapchain, &swapchainImageCount, m_Images.data());
ERROR_IF(Failed(result), "Failed getting swapchain {}'s images. Cause {}", m_Name, result)
THEN_ABORT(result);
vk::ImageViewCreateInfo viewCreateInfo = {
.viewType = vk::ImageViewType::e2D,
.format = m_Format,
.components = {.r = vk::ComponentSwizzle::eIdentity,
.g = vk::ComponentSwizzle::eIdentity,
.b = vk::ComponentSwizzle::eIdentity,
.a = vk::ComponentSwizzle::eIdentity},
.subresourceRange = {.aspectMask = vk::ImageAspectFlagBits::eColor,
.baseMipLevel = 0,
.levelCount = 1,
.baseArrayLayer = 0,
.layerCount = 1},
};
u32 index = 0;
for (auto image : m_Images)
{
viewCreateInfo.image = image;
vk::ImageView imageView;
result = device.createImageView(&viewCreateInfo, nullptr, &imageView);
ERROR_IF(Failed(result), "Failed creating swapchain {}'s image view [{}]. Cause {}", m_Name, index, result)
THEN_ABORT(result);
m_ImageViews.push_back(imageView);
++index;
}
DEBUG("Swapchain {} Image Views created.", m_Name);
}
void
Swapchain::Cleanup()
{
if (!m_ImageViews.empty()) // Don't want the condition in the logs.
DEBUG("Swapchain {} Image Views destroyed.", m_Name);
for (const auto imageView : m_ImageViews)
{
m_Device->m_Device.destroy(imageView, nullptr);
}
m_ImageViews.clear();
if (m_Swapchain)
{
m_Device->m_Device.destroy(m_Swapchain, nullptr);
m_Swapchain = nullptr;
DEBUG("Swapchain '{}' destroyed.", m_Name);
}
}

32
aster/swapchain.h Normal file
View File

@ -0,0 +1,32 @@
/// =============================================
// Aster: swapchain.h
// Copyright (c) 2020-2024 Anish Bhobe
// ==============================================
#pragma once
#include "global.h"
#include <EASTL/fixed_vector.h>
struct PhysicalDevice;
struct Window;
struct Device;
struct Swapchain final
{
const Device *m_Device;
vk::SwapchainKHR m_Swapchain;
NameString m_Name;
vk::Extent2D m_Extent;
vk::Format m_Format;
eastl::fixed_vector<vk::Image, 4> m_Images;
eastl::fixed_vector<vk::ImageView, 4> m_ImageViews;
Swapchain(const Window *window, const Device *device, NameString &&name);
~Swapchain();
void Create(const Window *window);
private:
void Cleanup();
};

89
aster/window.cpp Normal file
View File

@ -0,0 +1,89 @@
// =============================================
// Aster: window.cpp
// Copyright (c) 2020-2024 Anish Bhobe
// =============================================
#include "window.h"
#include "context.h"
#include "logger.h"
void
Window::SetWindowSize(const vk::Extent2D &extent) const noexcept
{
SetWindowSize(extent.width, extent.height);
}
void
Window::SetWindowSize(const u32 width, const u32 height) const noexcept
{
glfwSetWindowSize(m_Window, Cast<i32>(width), Cast<i32>(height));
}
vk::Extent2D
Window::GetSize() const
{
int width;
int height;
glfwGetWindowSize(m_Window, &width, &height);
return {Cast<u32>(width), Cast<u32>(height)};
}
Window::Window(const cstr title, Context *context, vk::Extent2D extent, const b8 isFullScreen)
{
m_Context = context;
m_Name = title;
GLFWmonitor *monitor = glfwGetPrimaryMonitor();
ERROR_IF(!monitor, "No monitor found");
i32 windowX, windowY, windowWidth, windowHeight;
glfwGetMonitorWorkarea(monitor, &windowX, &windowY, &windowWidth, &windowHeight);
glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API);
glfwWindowHint(GLFW_CENTER_CURSOR, GLFW_TRUE);
m_Window = glfwCreateWindow(Cast<i32>(extent.width), Cast<i32>(extent.height), m_Name.c_str(),
isFullScreen ? monitor : nullptr, nullptr);
ERROR_IF(m_Window == nullptr, "Window creation failed")
ELSE_DEBUG("Window '{}' created with resolution '{}x{}'", m_Name, extent.width, extent.height);
if (m_Window == nullptr)
{
const char *error = nullptr;
const auto code = glfwGetError(&error);
ERROR("GLFW Window Creation failed. Cause: ({}) {}", code, error)
THEN_ABORT(code);
}
if (isFullScreen == false)
{
glfwSetWindowPos(m_Window, Cast<i32>(windowWidth - extent.width) / 2,
Cast<i32>(windowHeight - extent.height) / 2);
}
glfwSetInputMode(m_Window, GLFW_CURSOR, GLFW_CURSOR_NORMAL);
VkSurfaceKHR surface;
auto result =
Cast<vk::Result>(glfwCreateWindowSurface(Cast<VkInstance>(m_Context->m_Instance), m_Window, nullptr, &surface));
ERROR_IF(Failed(result), "Failed to create Surface with {}", result)
THEN_ABORT(result)
ELSE_DEBUG("Surface {} Created", m_Name);
m_Surface = vk::SurfaceKHR(surface);
}
Window::~Window()
{
if (m_Context && m_Surface)
{
m_Context->m_Instance.destroy(m_Surface, nullptr);
DEBUG("Surface Destroyed");
}
if (m_Window != nullptr)
{
glfwDestroyWindow(m_Window);
m_Window = nullptr;
}
DEBUG("Window '{}' Destroyed", m_Name);
}

37
aster/window.h Normal file
View File

@ -0,0 +1,37 @@
// =============================================
// Aster: window.h
// Copyright (c) 2020-2024 Anish Bhobe
// =============================================
#pragma once
#include "global.h"
#include <EASTL/fixed_string.h>
struct Context;
struct Window final
{
// fields
Context *m_Context{};
GLFWwindow *m_Window = nullptr;
vk::SurfaceKHR m_Surface;
eastl::fixed_string<char, 32> m_Name;
// Methods
[[nodiscard]] bool
Poll() const noexcept
{
glfwPollEvents();
return !glfwWindowShouldClose(m_Window);
}
void SetWindowSize(const vk::Extent2D &extent) const noexcept;
void SetWindowSize(u32 width, u32 height) const noexcept;
[[nodiscard]] vk::Extent2D GetSize() const;
// Ctor/Dtor
Window(cstr title, Context *context, vk::Extent2D extent, b8 isFullScreen = false);
~Window();
};

View File

@ -1,25 +0,0 @@
# CMakeList.txt ; CMake project for Aster Core
cmake_minimum_required( VERSION 3.13 )
find_package( glm CONFIG REQUIRED )
find_package( glfw3 CONFIG REQUIRED )
find_path( SCOTTT_DEBUGBREAK_INCLUDE_DIRS "debugbreak.h" )
find_package( Vulkan REQUIRED )
# find_package( VulkanHeaders CONFIG REQUIRED )
find_package( VulkanMemoryAllocator CONFIG REQUIRED )
set( HEADER_FILES "constants.h" "config.h" "logger.h" "global.h" "context.h" "window.h" )
set( SOURCE_FILES "logger.cpp" "global.cpp" "context.cpp" "window.cpp" )
add_library( aster_core ${SOURCE_FILES} ${HEADER_FILES} )
set_property( TARGET aster_core PROPERTY CXX_STANDARD 23 )
target_link_libraries( aster_core PRIVATE glm::glm-header-only )
target_link_libraries( aster_core PRIVATE glfw )
target_include_directories( aster_core PRIVATE ${SCOTTT_DEBUGBREAK_INCLUDE_DIRS})
target_link_libraries( aster_core PRIVATE Vulkan::Vulkan Vulkan::Headers GPUOpen::VulkanMemoryAllocator )
add_executable( aster_exe "aster.cpp" )
target_link_libraries( aster_exe PRIVATE aster_core )
target_link_libraries( aster_exe PRIVATE glm::glm-header-only )

View File

@ -1,18 +0,0 @@
#include <iostream>
#include "constants.h"
#include "glfw_context.h"
#include "window.h"
int main(int, char**) {
GlfwContext* glfw = new GlfwContext();
Context* context = new Context("Aster", VERSION);
Window* window = new Window("Aster1", context, { 640, 480 });
delete window;
delete context;
delete glfw;
return 0;
}

View File

@ -1,137 +0,0 @@
// =============================================
// Aster: constants.h
// Copyright (c) 2020-2024 Anish Bhobe
// =============================================
#pragma once
#include <cstdint>
#include <cstdio>
#include <optional>
#include <tuple>
#include <glm/glm.hpp>
using c8 = char;
using u8 = uint8_t;
using u16 = uint16_t;
using u32 = uint32_t;
using u64 = uint64_t;
using i8 = int8_t;
using i16 = int16_t;
using i32 = int32_t;
using i64 = int64_t;
using f32 = float;
using f64 = double;
using f128 = long double;
using b8 = bool;
using b32 = u32;
using usize = size_t;
using p64 = intptr_t;
constexpr usize strlen_c(const char *s) {
usize len = 0;
char c = '\0';
do {
c = s[len];
len++;
} while (c != '\0');
return len;
}
constexpr auto ANSI_Black = "\u001b[30m";
constexpr auto ANSI_Red = "\u001b[31m";
constexpr auto ANSI_Green = "\u001b[32m";
constexpr auto ANSI_Yellow = "\u001b[33m";
constexpr auto ANSI_Blue = "\u001b[34m";
constexpr auto ANSI_Magenta = "\u001b[35m";
constexpr auto ANSI_Cyan = "\u001b[36m";
constexpr auto ANSI_White = "\u001b[37m";
constexpr auto ANSI_Reset = "\u001b[0m";
using std::forward;
using std::move;
using std::tie;
template <typename T>
using Option = std::optional<T>;
template <typename type_t, typename from_t>
constexpr auto cast(from_t &&_in) {
return static_cast<type_t>(forward<from_t>(_in));
}
template <typename type_t, typename from_t>
constexpr auto recast(from_t &&_in) {
return reinterpret_cast<type_t>(forward<from_t>(_in));
}
constexpr f32 operator""_deg(long double degrees) {
return glm::radians<f32>(cast<f32>(degrees));
}
constexpr f32 operator""_deg(unsigned long long int degrees) {
return glm::radians<f32>(cast<f32>(degrees));
}
constexpr f32 operator""_rad(long double rads) {
return cast<f32>(rads);
}
constexpr f32 operator""_rad(unsigned long long int rads) {
return cast<f32>(rads);
}
using glm::ivec2;
using glm::ivec3;
using glm::ivec4;
using glm::vec2;
using glm::vec3;
using glm::vec4;
using glm::mat2;
using glm::mat3;
using glm::mat4;
constexpr const char *PROJECT_NAME = "Aster";
struct Version {
u32 major;
u32 minor;
u32 patch;
};
constexpr Version VERSION = {
.major = 0,
.minor = 0,
.patch = 1,
};
enum class Error {
eUnknown = 1000,
eNoDevices = 1001,
};
template <typename T>
constexpr T max_value = std::numeric_limits<T>::max();
template <typename T>
constexpr T min_value = std::numeric_limits<T>::min();
template <typename T>
constexpr T lowest_value = std::numeric_limits<T>::lowest();
template <typename T>
constexpr T err_epsilon = std::numeric_limits<T>::epsilon();
template <typename T>
constexpr T positive_inf = std::numeric_limits<T>::infinity();
template <typename T>
constexpr T negative_inf = -std::numeric_limits<T>::infinity();
template <typename T>
constexpr T qnan = std::numeric_limits<T>::quiet_NaN();
template <typename T>
constexpr T snan = std::numeric_limits<T>::signalling_NaN();

View File

@ -1,99 +0,0 @@
// =============================================
// Aster: context.cpp
// Copyright (c) 2020-2024 Anish Bhobe
// =============================================
#include "context.h"
VKAPI_ATTR b32 VKAPI_CALL Context::debug_callback(VkDebugUtilsMessageSeverityFlagBitsEXT _message_severity, VkDebugUtilsMessageTypeFlagsEXT _message_type, const VkDebugUtilsMessengerCallbackDataEXT *_callback_data, [[maybe_unused]] void *_user_data) {
using Severity = vk::DebugUtilsMessageSeverityFlagsEXT;
using SeverityBits = vk::DebugUtilsMessageSeverityFlagBitsEXT;
using MessageType = vk::DebugUtilsMessageTypeFlagsEXT;
using MessageTypeBits = vk::DebugUtilsMessageTypeFlagBitsEXT;
const auto severity = Severity(_message_severity);
const auto message_type = MessageType(_message_type);
if (message_type & MessageTypeBits::eValidation) {
if (severity & SeverityBits::eError)
ERROR(_callback_data->pMessage);
if (severity & SeverityBits::eWarning)
WARN(_callback_data->pMessage);
if (severity & SeverityBits::eInfo)
INFO(_callback_data->pMessage);
if (severity & SeverityBits::eVerbose)
VERBOSE(_callback_data->pMessage);
}
return false;
}
void Context::init(const std::string_view &_app_name, const Version &_app_version) {
vk::Result result;
INFO_IF(enable_validation_layers, "Validation Layers enabled");
// Creating Instance
vk::ApplicationInfo app_info = {
.pApplicationName = _app_name.data(),
.applicationVersion = VK_MAKE_VERSION(_app_version.major, _app_version.minor, _app_version.patch),
.pEngineName = PROJECT_NAME,
.engineVersion = VK_MAKE_VERSION(VERSION.major, VERSION.minor, VERSION.patch),
.apiVersion = VK_API_VERSION_1_2,
};
vk::DebugUtilsMessengerCreateInfoEXT debug_messenger_create_info = {
.messageSeverity = vk::DebugUtilsMessageSeverityFlagBitsEXT::eError |
vk::DebugUtilsMessageSeverityFlagBitsEXT::eWarning |
vk::DebugUtilsMessageSeverityFlagBitsEXT::eInfo,
.messageType = vk::DebugUtilsMessageTypeFlagBitsEXT::eGeneral |
vk::DebugUtilsMessageTypeFlagBitsEXT::ePerformance |
vk::DebugUtilsMessageTypeFlagBitsEXT::eValidation,
.pfnUserCallback = debug_callback,
.pUserData = nullptr,
};
u32 glfw_extension_count = 0;
const char **glfw_extensions = nullptr;
glfw_extensions = glfwGetRequiredInstanceExtensions(&glfw_extension_count);
std::vector<const char *> vulkan_extensions(glfw_extensions, glfw_extensions + glfw_extension_count);
if (enable_validation_layers) {
vulkan_extensions.push_back(VK_EXT_DEBUG_UTILS_EXTENSION_NAME);
}
const vk::DynamicLoader dl;
// ReSharper disable once CppInconsistentNaming
const auto vkGetInstanceProcAddr = dl.getProcAddress<PFN_vkGetInstanceProcAddr>("vkGetInstanceProcAddr");
VULKAN_HPP_DEFAULT_DISPATCHER.init(vkGetInstanceProcAddr);
tie(result, instance) = vk::createInstance({
.pNext = enable_validation_layers ? &debug_messenger_create_info : nullptr,
.pApplicationInfo = &app_info,
.enabledLayerCount = enable_validation_layers ? cast<u32>(validation_layers.size()) : 0,
.ppEnabledLayerNames = enable_validation_layers ? validation_layers.data() : nullptr,
.enabledExtensionCount = cast<u32>(vulkan_extensions.size()),
.ppEnabledExtensionNames = vulkan_extensions.data(),
});
ERROR_IF(failed(result), "Failed to create Vulkan instance with "s + to_string(result))
THEN_CRASH(result)
ELSE_INFO("Instance Created.");
VULKAN_HPP_DEFAULT_DISPATCHER.init(instance);
// Debug Messenger
if (enable_validation_layers) {
tie(result, debug_messenger) = instance.createDebugUtilsMessengerEXT(debug_messenger_create_info);
ERROR_IF(failed(result), "Debug Messenger creation failed with "s + to_string(result))
ELSE_INFO("Debug Messenger Created.");
}
}
Context::~Context() {
if (instance) {
if (enable_validation_layers && debug_messenger) {
instance.destroyDebugUtilsMessengerEXT(debug_messenger);
}
instance.destroy();
INFO("Context destroyed");
}
}

View File

@ -1,86 +0,0 @@
// =============================================
// Aster: context.h
// Copyright (c) 2020-2024 Anish Bhobe
// =============================================
#pragma once
#include "global.h"
#include <utility>
#include <vector>
/**
* @class Context
*
* @brief Vulkan context to handle device initialization logic.
*
* Handles the required hardware interactions.
*/
class Context final {
public:
Context(const std::string_view &_app_name, const Version &_app_version, const b8 _enable_validation = true) :
enable_validation_layers{ _enable_validation } {
init(_app_name, _app_version);
}
Context(const std::string_view &_app_name, const Version &_app_version, const std::vector<const char *> &_additional_device_extensions, const b8 _enable_validation = true) :
enable_validation_layers{ _enable_validation } {
device_extensions.reserve(device_extensions.size() + _additional_device_extensions.size());
device_extensions.insert(device_extensions.end(), _additional_device_extensions.begin(), _additional_device_extensions.end());
init(_app_name, _app_version);
}
Context(const std::string_view &_app_name, const Version &_app_version, const std::vector<const char *> &_additional_device_extensions, const std::vector<const char *> &_additional_validation_layers) {
device_extensions.reserve(device_extensions.size() + _additional_device_extensions.size());
device_extensions.insert(device_extensions.end(), _additional_device_extensions.begin(), _additional_device_extensions.end());
validation_layers.reserve(validation_layers.size() + _additional_validation_layers.size());
validation_layers.insert(validation_layers.end(), _additional_validation_layers.begin(), _additional_validation_layers.end());
init(_app_name, _app_version);
}
Context(const Context &_other) = delete;
Context(Context &&_other) noexcept :
enable_validation_layers{ _other.enable_validation_layers }, validation_layers{ std::move(_other.validation_layers) }, device_extensions{ std::move(_other.device_extensions) }, instance{ std::exchange(_other.instance, nullptr) }, debug_messenger{ std::exchange(_other.debug_messenger, nullptr) } {}
Context &operator=(const Context &_other) = delete;
Context &operator=(Context &&_other) noexcept {
if (this == &_other)
return *this;
enable_validation_layers = _other.enable_validation_layers;
validation_layers = std::move(_other.validation_layers);
device_extensions = std::move(_other.device_extensions);
instance = std::exchange(_other.instance, nullptr);
debug_messenger = std::exchange(_other.debug_messenger, nullptr);
return *this;
}
~Context();
// Fields
bool enable_validation_layers{ true };
std::vector<const char *> validation_layers = {
"VK_LAYER_KHRONOS_validation",
};
std::vector<const char *> device_extensions = {
VK_KHR_SWAPCHAIN_EXTENSION_NAME,
VK_KHR_MULTIVIEW_EXTENSION_NAME,
};
vk::Instance instance;
vk::DebugUtilsMessengerEXT debug_messenger;
private:
void init(const std::string_view &_app_name, const Version &_app_version);
static VKAPI_ATTR b32 VKAPI_CALL debug_callback(VkDebugUtilsMessageSeverityFlagBitsEXT _message_severity,
VkDebugUtilsMessageTypeFlagsEXT _message_type,
const VkDebugUtilsMessengerCallbackDataEXT *_callback_data,
void *_user_data);
};

View File

@ -1,33 +0,0 @@
// =============================================
// Aster: glfw_context.h
// Copyright (c) 2020-2024 Anish Bhobe
// =============================================
#pragma once
#include "global.h"
struct GlfwContext {
static i32 post_error() noexcept {
static const char* error_ = nullptr;
const auto code = glfwGetError(&error_);
ERROR("GLFW "s + error_);
return code;
}
inline static u32 count = 0;
GlfwContext() {
if (count++ > 0) return;
if (glfwInit() == GLFW_FALSE) {
CRASH(post_error());
}
}
~GlfwContext() {
if (--count == 0) {
glfwTerminate();
}
count = 0;
}
};

View File

@ -1,72 +0,0 @@
// =============================================
// Aster: global.cpp
// Copyright (c) 2020-2024 Anish Bhobe
// =============================================
#include "global.h"
#include <cstdarg>
#include <cstdio>
#define VMA_IMPLEMENTATION
#include <vma/vk_mem_alloc.h>
// NOTE: Vulkan Dispatch Loader Storage - Should only appear once.
VULKAN_HPP_DEFAULT_DISPATCH_LOADER_DYNAMIC_STORAGE
//int Vsnprintf8(char* pDestination, size_t n, const char* pFormat, va_list arguments) {
// //ERROR_IF(pDestination == nullptr, "Null buffer") THEN_CRASH(1) ELSE_IF_ERROR(n == 0, "Empty buffer") THEN_CRASH(1);
//#ifdef _MSC_VER
// auto v = vsnprintf(pDestination, n, pFormat, arguments);
// ERROR_IF(v == 0, "Final requirement cannot be 0") THEN_CRASH(1);
// return v;
//#else
// return vsnprintf(pDestination, n, pFormat, arguments);
//#endif
//}
//
//int VsnprintfW(wchar_t* pDestination, size_t n, const wchar_t* pFormat, va_list arguments) {
// //ERROR_IF(pDestination == nullptr, "Null buffer") THEN_CRASH(1) ELSE_IF_ERROR(n == 0, "Empty buffer") THEN_CRASH(1);
//#ifdef _MSC_VER
// if (pDestination == nullptr && n == 0) {
// return _vscwprintf(pFormat, arguments);
// } else {
// return _vsnwprintf_s(pDestination, n, _TRUNCATE, pFormat, arguments);
// }
//#else
// char* d = new char[n + 1];
// int r = vsnprintf(d, n, convertstring<char16_t, char>(pFormat).c_str(), arguments);
// memcpy(pDestination, convertstring<char, char16_t>(d).c_str(), (n + 1) * sizeof(char16_t));
// delete[] d;
// return r;
//#endif
//}
//
//int Vsnprintf16(char16_t* pDestination, size_t n, const char16_t* pFormat, va_list arguments) {
// //ERROR_IF(pDestination == nullptr, "Null buffer") THEN_CRASH(1) ELSE_IF_ERROR(n == 0, "Empty buffer") THEN_CRASH(1);
//#ifdef _MSC_VER
// if (pDestination == nullptr && n == 0) {
// return _vscwprintf((wchar_t*)pFormat, arguments);
// } else {
// return _vsnwprintf_s((wchar_t*)pDestination, n, _TRUNCATE, (wchar_t*)pFormat, arguments);
// }
//#else
// char* d = new char[n + 1];
// int r = vsnprintf(d, n, convertstring<char16_t, char>(pFormat).c_str(), arguments);
// memcpy(pDestination, convertstring<char, char16_t>(d).c_str(), (n + 1) * sizeof(char16_t));
// delete[] d;
// return r;
//#endif
//}
std::string std::impl::format(const char *_fmt, ...) {
va_list args;
va_start(args, _fmt);
const auto req = vsnprintf(nullptr, 0, _fmt, args) + 1;
string buf(req, '\0');
vsnprintf(buf.data(), buf.size(), _fmt, args);
va_end(args);
return buf;
}

View File

@ -1,100 +0,0 @@
// =============================================
// Aster: global.h
// Copyright (c) 2020-2024 Anish Bhobe
// =============================================
#pragma once
#include "config.h"
#include "constants.h"
#include "logger.h"
#include <GLFW/glfw3.h>
#include <glm/glm.hpp>
#include <glm/gtc/matrix_transform.hpp>
#include <string>
#define VULKAN_HPP_ASSERT(expr) DEBUG_IF(!(expr), "Vulkan assert failed")
#include <vulkan/vulkan.hpp>
#include <vk_mem_alloc.h>
#define CODE_LOC " @ " __FILE__ ":" VULKAN_HPP_STRINGIFY(__LINE__)
[[nodiscard]] inline bool failed(const vk::Result _result) {
return _result != vk::Result::eSuccess;
}
namespace std {
namespace impl {
string format(const char *_fmt, ...);
}
template <typename... Ts>
[[nodiscard]] string fmt(const char *_fmt, Ts &&..._args) {
return impl::format(_fmt, forward<Ts>(_args)...);
}
} // namespace std
template <typename T>
concept IsVkEnum = requires(T _t) {
{ std::is_enum_v<T> };
{ vk::to_string(_t) } -> std::same_as<std::string>;
};
template <typename T>
requires IsVkEnum<T> [[nodiscard]] const char *to_cstr(const T &_val) {
static std::string buffer;
buffer = vk::to_string(_val);
return buffer.c_str();
}
// TODO: Check why inline namespaces aren't working in MSVC 19.27.29110
using namespace std::literals::string_literals;
using namespace std::literals::string_view_literals;
template <typename T>
requires vk::isVulkanHandleType<T>::value [[nodiscard]] constexpr u64
get_vk_handle(const T &_vk_handle) noexcept {
return reinterpret_cast<u64>(cast<T::CType>(_vk_handle));
}
template <typename F>
struct std::hash<vk::Flags<F>> {
[[nodiscard]] usize operator()(const vk::Flags<F> &_val) {
return std::hash<u32>()(cast<u32>(_val));
}
};
template <typename T>
[[nodiscard]] usize hash_any(const T &_val) {
return std::hash<std::remove_cvref_t<T>>()(_val);
}
[[nodiscard]] inline usize hash_combine(const usize _hash0,
const usize _hash1) {
constexpr usize salt_value = 0x9e3779b9;
return _hash0 ^ (_hash1 + salt_value + (_hash0 << 6) + (_hash0 >> 2));
}
struct Time {
static constexpr f64 max_delta = 0.1;
inline static f64 elapsed{ qnan<f64> };
inline static f64 delta{ qnan<f64> };
static void init() {
WARN_IF(!std::isnan(elapsed), "Time already init.");
elapsed = glfwGetTime();
delta = 1.0 / 60.0;
}
static void update() {
ERROR_IF(std::isnan(elapsed), "Time not init.");
const auto new_elapsed = glfwGetTime();
delta = std::clamp(new_elapsed - elapsed, 0.0, max_delta);
elapsed = new_elapsed;
}
};
[[nodiscard]] inline usize closest_multiple(const usize _val, const usize _of) {
return _of * ((_val + _of - 1) / _of);
}

View File

@ -1,178 +0,0 @@
// =============================================
// Aster: logger.h
// Copyright (c) 2020-2024 Anish Bhobe
// =============================================
#pragma once
#include "constants.h"
#include <debugbreak.h>
#include <string>
struct Logger {
enum class LogType : u32 {
eError,
eWarning,
eInfo,
eDebug,
eVerbose,
};
u32 minimum_logging_level{ cast<u32>(LogType::eDebug) };
void set_minimum_logging_level(LogType _log_type) {
minimum_logging_level = cast<u32>(_log_type);
}
template <LogType LogLevel>
constexpr static const char *to_cstr() {
if constexpr (LogLevel == LogType::eError)
return "[ERROR]:";
if constexpr (LogLevel == LogType::eWarning)
return "[WARN]: ";
if constexpr (LogLevel == LogType::eInfo)
return "[INFO]: ";
if constexpr (LogLevel == LogType::eDebug)
return "[DEBUG]:";
if constexpr (LogLevel == LogType::eVerbose)
return "[VERB]: ";
}
template <LogType LogLevel>
constexpr static const char *to_color_cstr() {
if constexpr (LogLevel == LogType::eError)
return ANSI_Red;
if constexpr (LogLevel == LogType::eWarning)
return ANSI_Yellow;
if constexpr (LogLevel == LogType::eInfo)
return ANSI_Green;
if constexpr (LogLevel == LogType::eDebug)
return ANSI_White;
if constexpr (LogLevel == LogType::eVerbose)
return ANSI_Blue;
}
template <LogType LogLevel>
void log(const std::string_view &_message, const char *_loc, u32 _line) const {
if (cast<u32>(LogLevel) <= minimum_logging_level) {
printf("%s%s %s%s| at %s:%u%s\n", to_color_cstr<LogLevel>(), to_cstr<LogLevel>(), _message.data(), ANSI_Black, _loc, _line, ANSI_Reset);
}
#if !defined(NDEBUG)
if constexpr (LogLevel == LogType::eError) {
debug_break();
}
#endif // !defined(NDEBUG)
}
template <LogType LogLevel>
void log_cond(const char *_expr_str, const std::string_view &_message, const char *_loc, u32 _line) const {
if (cast<u32>(LogLevel) <= minimum_logging_level) {
printf("%s%s (%s) %s%s| at %s:%u%s\n", to_color_cstr<LogLevel>(), to_cstr<LogLevel>(), _expr_str, _message.data(), ANSI_Black, _loc, _line, ANSI_Reset);
}
#if !defined(NDEBUG)
if constexpr (LogLevel == LogType::eError) {
debug_break();
}
#endif // !defined(NDEBUG)
}
};
extern Logger g_logger;
#define ERROR(msg) g_logger.log<Logger::LogType::eError>(msg, __FILE__, __LINE__)
#define WARN(msg) g_logger.log<Logger::LogType::eWarning>(msg, __FILE__, __LINE__)
#define INFO(msg) g_logger.log<Logger::LogType::eInfo>(msg, __FILE__, __LINE__)
#define ERROR_IF(expr, msg) \
if (cast<bool>(expr)) [[unlikely]] \
g_logger.log_cond<Logger::LogType::eError>(#expr, msg, __FILE__, __LINE__)
#define WARN_IF(expr, msg) \
if (cast<bool>(expr)) [[unlikely]] \
g_logger.log_cond<Logger::LogType::eWarning>(#expr, msg, __FILE__, __LINE__)
#define INFO_IF(expr, msg) \
if (cast<bool>(expr)) \
g_logger.log_cond<Logger::LogType::eInfo>(#expr, msg, __FILE__, __LINE__)
#define ELSE_IF_ERROR(expr, msg) \
; \
else if (cast<bool>(expr)) [[unlikely]] g_logger.log_cond<Logger::LogType::eError>(#expr, msg, __FILE__, __LINE__)
#define ELSE_IF_WARN(expr, msg) \
; \
else if (cast<bool>(expr)) [[unlikely]] g_logger.log_cond<Logger::LogType::eWarning>(#expr, msg, __FILE__, __LINE__)
#define ELSE_IF_INFO(expr, msg) \
; \
else if (cast<bool>(expr)) g_logger.log_cond<Logger::LogType::eInfo>(#expr, msg, __FILE__, __LINE__)
#define ELSE_ERROR(msg) \
; \
else [[unlikely]] g_logger.log<Logger::LogType::eError>(msg, __FILE__, __LINE__)
#define ELSE_WARN(msg) \
; \
else [[unlikely]] g_logger.log<Logger::LogType::eWarning>(msg, __FILE__, __LINE__)
#define ELSE_INFO(msg) \
; \
else g_logger.log<Logger::LogType::eInfo>(msg, __FILE__, __LINE__)
#if !defined(DEBUG_LOG_DISABLED) && !defined(NDEBUG)
#define DEBUG(msg) g_logger.log<Logger::LogType::eDebug>(msg, __FILE__, __LINE__)
#define DEBUG_IF(expr, msg) \
if (cast<bool>(expr)) \
g_logger.log_cond<Logger::LogType::eDebug>(#expr, msg, __FILE__, __LINE__)
#define ELSE_IF_DEBUG(expr, msg) \
; \
else if (cast<bool>(expr)) g_logger.log_cond<Logger::LogType::eDebug>(#expr, msg, __FILE__, __LINE__)
#define ELSE_DEBUG(msg) \
; \
else [[unlikely]] g_logger.log<Logger::LogType::eDebug>(msg, __FILE__, __LINE__)
#else // !defined(DEBUG_LOG_DISABLED)
#define DEBUG(msg) \
{}
#define DEBUG_IF(expr, msg) \
if (expr) \
(void)msg
#define ELSE_IF_DEBUG(expr, msg) \
; \
if (expr) \
(void)msg
#define ELSE_DEBUG(msg) \
; \
{}
#endif // !defined(DEBUG_LOG_DISABLED)
#if !defined(VERBOSE_LOG_DISABLED) && !defined(NDEBUG)
#define VERBOSE(msg) g_logger.log<Logger::LogType::eVerbose>(msg, __FILE__, __LINE__)
#define VERBOSE_IF(expr, msg) \
if (cast<bool>(expr)) \
g_logger.log_cond<Logger::LogType::eVerbose>(#expr, msg, __FILE__, __LINE__)
#define ELSE_IF_VERBOSE(expr, msg) \
; \
else if (cast<bool>(expr)) g_logger.log_cond<Logger::LogType::eVerbose>(#expr, msg, __FILE__, __LINE__)
#define ELSE_VERBOSE(msg) \
; \
else g_logger.log<Logger::LogType::eVerbose>(msg, __FILE__, __LINE__)
#else // !defined(DEBUG_LOG_DISABLED)
#define VERBOSE(msg) \
{}
#define VERBOSE_IF(expr, msg) \
if (expr) \
(void)msg
#define ELSE_IF_VERBOSE(expr, msg) \
; \
if (expr) \
(void)msg
#define ELSE_VERBOSE(msg) \
; \
{}
#endif // !defined(VERBOSE_LOG_DISABLED)
#define DO(code) , code
#define CRASH(code) exit(cast<i32>(code))
#define THEN_CRASH(code) , CRASH(code)

View File

@ -1,78 +0,0 @@
// =============================================
// Aster: window.cpp
// Copyright (c) 2020-2024 Anish Bhobe
// =============================================
#include "window.h"
#include "logger.h"
#include "context.h"
#include "glfw_context.h"
Window::Window(const std::string_view& _title, Context* _context, vk::Extent2D _extent, b8 _full_screen)
: parent_context{ std::move(_context) }
, extent{ _extent }
, name{ _title }
, full_screen{ _full_screen } {
monitor = glfwGetPrimaryMonitor();
ERROR_IF(monitor == nullptr, "No monitor found");
i32 x_, y_, w_, h_;
glfwGetMonitorWorkarea(monitor, &x_, &y_, &w_, &h_);
glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API);
glfwWindowHint(GLFW_CENTER_CURSOR, GLFW_TRUE);
window = glfwCreateWindow(extent.width, extent.height, name.data(), full_screen ? monitor : nullptr, nullptr);
ERROR_IF(window == nullptr, "Window creation failed") ELSE_INFO(std::fmt("Window '%s' created with resolution '%dx%d'", name.data(), extent.width, extent.height));
if (window == nullptr) {
auto code = GlfwContext::post_error();
glfwTerminate();
CRASH(code);
}
if (full_screen == false) {
glfwSetWindowPos(window, (w_ - extent.width) / 2, (h_ - extent.height) / 2);
}
glfwSetInputMode(window, GLFW_CURSOR, GLFW_CURSOR_NORMAL);
VkSurfaceKHR surface_;
auto result = cast<vk::Result>(glfwCreateWindowSurface(cast<VkInstance>(_context->instance), window, nullptr, &surface_));
ERROR_IF(failed(result), "Failed to create Surface with "s + to_string(result)) THEN_CRASH(result) ELSE_INFO("Surface Created");
surface = vk::SurfaceKHR(surface_);
}
Window::Window(Window&& _other) noexcept: parent_context{ std::move(_other.parent_context) }
, window{ std::exchange(_other.window, nullptr) }
, monitor{ _other.monitor }
, surface{ std::exchange(_other.surface, nullptr) }
, extent{ _other.extent }
, name{ std::move(_other.name) }
, full_screen{ _other.full_screen } {}
Window& Window::operator=(Window&& _other) noexcept {
if (this == &_other) return *this;
parent_context = std::move(_other.parent_context);
window = _other.window;
monitor = _other.monitor;
surface = std::exchange(_other.surface, nullptr);
extent = _other.extent;
name = std::move(_other.name);
full_screen = _other.full_screen;
return *this;
}
Window::~Window() {
if (parent_context && surface) {
parent_context->instance.destroy(surface);
INFO("Surface Destroyed");
}
if (window != nullptr) {
glfwDestroyWindow(window);
window = nullptr;
}
monitor = nullptr;
INFO("Window '" + name + "' Destroyed");
}

View File

@ -1,50 +0,0 @@
// =============================================
// Aster: window.h
// Copyright (c) 2020-2024 Anish Bhobe
// =============================================
#pragma once
#include "global.h"
#include "context.h"
struct Window final {
Window(const std::string_view& _title, Context* _context, vk::Extent2D _extent, b8 _full_screen = false);
Window(const Window& _other) = delete;
Window(Window&& _other) noexcept;
Window& operator=(const Window& _other) = delete;
Window& operator=(Window&& _other) noexcept;
~Window();
bool should_close() const noexcept {
return glfwWindowShouldClose(window);
}
bool poll() const noexcept {
glfwPollEvents();
return !glfwWindowShouldClose(window);
}
void set_window_size(const vk::Extent2D& _extent) noexcept {
extent = _extent;
glfwSetWindowSize(window, extent.width, extent.height);
}
void set_window_size(const u32 _width, const u32 _height) noexcept {
set_window_size({ _width, _height });
}
// fields
Context* parent_context{};
GLFWwindow* window{ nullptr };
GLFWmonitor* monitor{ nullptr };
vk::SurfaceKHR surface;
vk::Extent2D extent;
std::string name;
b8 full_screen{ false };
};

View File

@ -0,0 +1,8 @@
# CMakeList.txt ; CMake project for Triangle
cmake_minimum_required(VERSION 3.13)
add_library(util_helper STATIC helpers.h helpers.cpp)
target_link_libraries(util_helper PRIVATE aster_core)
target_include_directories(util_helper PUBLIC ${CMAKE_CURRENT_SOURCE_DIR})

View File

@ -0,0 +1,90 @@
// =============================================
// Aster: helpers.cpp
// Copyright (c) 2020-2024 Anish Bhobe
// =============================================
#include "helpers.h"
#include "device.h"
#include "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 == 1024);
return outputVec;
}

18
samples/00_util/helpers.h Normal file
View File

@ -0,0 +1,18 @@
// =============================================
// Aster: helpers.h
// Copyright (c) 2020-2024 Anish Bhobe
// =============================================
#pragma once
#include "global.h"
#include "queue_allocation.h"
#include <EASTL/vector.h>
struct PhysicalDevice;
class PhysicalDevices;
PhysicalDevice FindSuitableDevice(const PhysicalDevices &physicalDevices);
QueueAllocation FindAppropriateQueueAllocation(const PhysicalDevice *physicalDevice);
eastl::vector<u32> ReadFile(cstr fileName);

View File

@ -0,0 +1,10 @@
# CMakeList.txt ; CMake project for Triangle
cmake_minimum_required(VERSION 3.13)
add_executable(triangle "triangle.cpp")
add_shader(triangle shader/triangle.vert.glsl)
add_shader(triangle shader/triangle.frag.glsl)
target_link_libraries(triangle PRIVATE aster_core)
target_link_libraries(triangle PRIVATE util_helper)

View File

@ -0,0 +1,9 @@
#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,27 @@
#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

@ -0,0 +1,28 @@
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

@ -0,0 +1,540 @@
// =============================================
// Aster: triangle.cpp
// Copyright (c) 2020-2024 Anish Bhobe
// =============================================
#include "buffer.h"
#include "constants.h"
#include "context.h"
#include "device.h"
#include "physical_device.h"
#include "window.h"
#include "global.h"
#include "pipeline.h"
#include "swapchain.h"
#include "helpers.h"
#include <EASTL/array.h>
constexpr u32 MAX_FRAMES_IN_FLIGHT = 3;
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
{
float m_Position[4];
float m_Color[4];
constexpr static vk::VertexInputBindingDescription
GetBinding(const u32 binding)
{
return {.binding = binding, .stride = sizeof(Vertex), .inputRate = vk::VertexInputRate::eVertex};
}
constexpr static eastl::array<vk::VertexInputAttributeDescription, 2>
GetAttributes(const u32 binding)
{
return {
vk::VertexInputAttributeDescription{
.location = 0,
.binding = binding,
.format = vk::Format::eR32G32B32A32Sfloat,
.offset = offsetof(Vertex, m_Position),
},
vk::VertexInputAttributeDescription{
.location = 1,
.binding = binding,
.format = vk::Format::eR32G32B32A32Sfloat,
.offset = offsetof(Vertex, m_Color),
},
};
}
};
struct Frame
{
const Device *m_Device;
vk::CommandPool m_Pool;
vk::Fence m_FrameAvailableFence;
vk::Semaphore m_ImageAcquireSem;
vk::Semaphore m_RenderFinishSem;
Frame(const Device *device, u32 queueFamilyIndex, u32 frameCount);
~Frame();
[[nodiscard]] vk::CommandBuffer AllocateCommandBuffer() const;
};
int
main(int, char **)
{
MIN_LOG_LEVEL(Logger::LogType::eInfo);
Context context = {"Triangle", VERSION};
Window window = {"Triangle (Aster)", &context, {640, 480}};
PhysicalDevices physicalDevices = {&window, &context};
PhysicalDevice deviceToUse = FindSuitableDevice(physicalDevices);
INFO("Using {} as the primary device.", deviceToUse.m_DeviceProperties.deviceName.data());
Features enabledDeviceFeatures = {.m_Vulkan13Features = {.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 = {&window, &device, "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 vertices = {
Vertex{.m_Position = {-0.5f, -0.5f, 0.0f, 1.0f}, .m_Color = {1.0f, 0.0f, 0.0f, 1.0f}},
Vertex{.m_Position = {0.5f, -0.5f, 0.0f, 1.0f}, .m_Color = {0.0f, 1.0f, 0.0f, 1.0f}},
Vertex{.m_Position = {0.0f, 0.5f, 0.0f, 1.0f}, .m_Color = {0.0f, 0.0f, 1.0f, 1.0f}},
};
VertexBuffer vbo;
vbo.Init(&device, vertices.size() * sizeof vertices[0], "VBO");
{
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
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 = {
.aspectMask = vk::ImageAspectFlagBits::eColor,
.baseMipLevel = 0,
.levelCount = 1,
.baseArrayLayer = 0,
.layerCount = 1,
};
vk::ImageMemoryBarrier topOfThePipeBarrier = {
.oldLayout = vk::ImageLayout::eUndefined,
.newLayout = vk::ImageLayout::eColorAttachmentOptimal,
.srcQueueFamilyIndex = queueAllocation.m_Family,
.dstQueueFamilyIndex = queueAllocation.m_Family,
.subresourceRange = subresourceRange,
};
vk::ImageMemoryBarrier renderToPresentBarrier = {
.oldLayout = vk::ImageLayout::eColorAttachmentOptimal,
.newLayout = vk::ImageLayout::ePresentSrcKHR,
.srcQueueFamilyIndex = queueAllocation.m_Family,
.dstQueueFamilyIndex = queueAllocation.m_Family,
.subresourceRange = subresourceRange,
};
// Frames
eastl::fixed_vector<Frame, MAX_FRAMES_IN_FLIGHT> frames;
for (int i = 0; i < MAX_FRAMES_IN_FLIGHT; ++i)
{
frames.emplace_back(&device, queueAllocation.m_Family, i);
}
INFO("Starting loop");
u32 frameIndex = 0;
while (window.Poll())
{
Frame *currentFrame = &frames[frameIndex];
auto result = device.m_Device.waitForFences(1, &currentFrame->m_FrameAvailableFence, true, MaxValue<u64>);
ERROR_IF(Failed(result), "Waiting for fence {} failed. Cause: {}", frameIndex, result)
THEN_ABORT(result);
u32 imageIndex;
result = device.m_Device.acquireNextImageKHR(swapchain.m_Swapchain, MaxValue<u64>,
currentFrame->m_ImageAcquireSem, nullptr, &imageIndex);
if (Failed(result))
{
switch (result)
{
case vk::Result::eErrorOutOfDateKHR:
case vk::Result::eSuboptimalKHR:
INFO("Recreating Swapchain. Cause: {}", result);
swapchain.Create(&window);
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, {});
ERROR_IF(Failed(result), "Command pool {} reset failed. Cause: {}", frameIndex, result)
THEN_ABORT(result);
vk::ImageView currentImageView = swapchain.m_ImageViews[imageIndex];
vk::Image currentImage = swapchain.m_Images[imageIndex];
vk::CommandBuffer cmd = currentFrame->AllocateCommandBuffer();
topOfThePipeBarrier.image = currentImage;
renderToPresentBarrier.image = currentImage;
vk::CommandBufferBeginInfo beginInfo = {.flags = vk::CommandBufferUsageFlagBits::eOneTimeSubmit};
result = cmd.begin(&beginInfo);
ERROR_IF(Failed(result), "Command buffer begin failed. Cause: {}", result)
THEN_ABORT(result);
cmd.pipelineBarrier(vk::PipelineStageFlagBits::eTopOfPipe, vk::PipelineStageFlagBits::eColorAttachmentOutput,
{}, 0, nullptr, 0, nullptr, 1, &topOfThePipeBarrier);
// Render
vk::RenderingAttachmentInfo attachmentInfo = {
.imageView = currentImageView,
.imageLayout = vk::ImageLayout::eColorAttachmentOptimal,
.resolveMode = vk::ResolveModeFlagBits::eNone,
.loadOp = vk::AttachmentLoadOp::eClear,
.storeOp = vk::AttachmentStoreOp::eStore,
.clearValue = vk::ClearColorValue{0.0f, 0.0f, 0.0f, 1.0f},
};
vk::RenderingInfo renderingInfo = {
.renderArea = {.extent = swapchain.m_Extent},
.layerCount = 1,
.colorAttachmentCount = 1,
.pColorAttachments = &attachmentInfo,
};
cmd.beginRendering(&renderingInfo);
cmd.setViewport(0, 1, &viewport);
cmd.setScissor(0, 1, &scissor);
cmd.bindPipeline(vk::PipelineBindPoint::eGraphics, pipeline.m_Pipeline);
usize offsets = 0;
cmd.bindVertexBuffers(0, 1, &vbo.m_Buffer, &offsets);
cmd.draw(3, 1, 0, 0);
cmd.endRendering();
cmd.pipelineBarrier(vk::PipelineStageFlagBits::eColorAttachmentOutput, vk::PipelineStageFlagBits::eBottomOfPipe,
{}, 0, nullptr, 0, nullptr, 1, &renderToPresentBarrier);
result = cmd.end();
ERROR_IF(Failed(result), "Command buffer end failed. Cause: {}", result)
THEN_ABORT(result);
vk::PipelineStageFlags waitDstStage = vk::PipelineStageFlagBits::eColorAttachmentOutput;
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(&window);
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;
}
auto result = device.m_Device.waitIdle();
ERROR_IF(Failed(result), "Wait idle failed. Cause: {}", result);
device.m_Device.destroy(copyPool, nullptr);
vbo.Destroy(&device);
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);
DEBUG("Frame {} created successfully.", frameCount);
}
vk::CommandBuffer
Frame::AllocateCommandBuffer() const
{
const vk::CommandBufferAllocateInfo allocateInfo = {
.commandPool = m_Pool, .level = vk::CommandBufferLevel::ePrimary, .commandBufferCount = 1};
vk::CommandBuffer commandBuffer;
vk::Result result = m_Device->m_Device.allocateCommandBuffers(&allocateInfo, &commandBuffer);
ERROR_IF(Failed(result), "Command buffer allocation failed. Cause: {}", result)
THEN_ABORT(result);
return commandBuffer;
}
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");
}

8
samples/CMakeLists.txt Normal file
View File

@ -0,0 +1,8 @@
# CMakeList.txt ; CMake project for Samples
cmake_minimum_required(VERSION 3.13)
add_subdirectory("01_triangle")
add_subdirectory("00_util")

View File

@ -1,8 +1,10 @@
{
"dependencies": [
"fmt",
"glfw3",
"glm",
"scottt-debugbreak",
"vulkan-memory-allocator"
"vulkan-memory-allocator",
"eastl"
]
}