Compare commits
43 Commits
Author | SHA1 | Date | |
---|---|---|---|
c62f727052 | |||
b0e204f03c | |||
47a2ea4ebf | |||
9738de012a | |||
ae707f1f3d | |||
a31ef34262 | |||
7d2b9e6f1b | |||
72a5a1d901 | |||
7bec16c7b9 | |||
798be67cc5 | |||
3088a42a07 | |||
f9232f314b | |||
160ca7070f | |||
c5e1a66e36 | |||
ebe78cb4ca | |||
d30b37bfbd | |||
b1ac87d304 | |||
72d32fba6f | |||
92af78a6b0 | |||
bfacea2bd4 | |||
f5a06e6ac1 | |||
1824b73d66 | |||
b0005c250f | |||
2b39c6e15b | |||
89cdf712b8 | |||
d53fb94e4c | |||
b87c2164ad | |||
0205f70ba6 | |||
ece828d045 | |||
474b096337 | |||
9c78fe09c9 | |||
bd8eda13aa | |||
91afd4ad1b | |||
1f1e62abdc | |||
986719a405 | |||
a31c7ab55b | |||
ccfbc86f4f | |||
0c0c46e23d | |||
b48df32331 | |||
581db91157 | |||
73445af2e7 | |||
9b6e333652 | |||
af7d19a956 |
1
.gitignore
vendored
1
.gitignore
vendored
@ -1 +1,2 @@
|
|||||||
.vs/
|
.vs/
|
||||||
|
build/
|
||||||
|
8
CMakeLists.txt
Normal file
8
CMakeLists.txt
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
# Main NF CMakeLists.txt
|
||||||
|
cmake_minimum_required(VERSION 3.20)
|
||||||
|
|
||||||
|
project(nf)
|
||||||
|
|
||||||
|
add_subdirectory(NothinFancy)
|
||||||
|
|
||||||
|
add_subdirectory(TestGame)
|
49
CMakePresets.json
Normal file
49
CMakePresets.json
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
// NF CMake presets
|
||||||
|
{
|
||||||
|
"version": 3,
|
||||||
|
|
||||||
|
"configurePresets": [
|
||||||
|
{
|
||||||
|
"name": "base",
|
||||||
|
"hidden": true,
|
||||||
|
"generator": "Ninja",
|
||||||
|
"binaryDir": "${sourceDir}/build/${presetName}",
|
||||||
|
"architecture": {
|
||||||
|
"value": "x64",
|
||||||
|
"strategy": "external"
|
||||||
|
},
|
||||||
|
"cacheVariables": {
|
||||||
|
"CMAKE_C_COMPILER": "cl.exe",
|
||||||
|
"CMAKE_CXX_COMPILER": "cl.exe"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
"name": "Debug",
|
||||||
|
"inherits": "base",
|
||||||
|
"cacheVariables": {
|
||||||
|
"CMAKE_BUILD_TYPE": "Debug"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
"name": "Release",
|
||||||
|
"inherits": "base",
|
||||||
|
"cacheVariables": {
|
||||||
|
"CMAKE_BUILD_TYPE": "Release"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
|
||||||
|
"buildPresets": [
|
||||||
|
{
|
||||||
|
"name": "Debug",
|
||||||
|
"configurePreset": "Debug"
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
"name": "Release",
|
||||||
|
"configurePreset": "Release"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
44
NothinFancy/CMakeLists.txt
Normal file
44
NothinFancy/CMakeLists.txt
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
# NF library CMakeLists.txt
|
||||||
|
add_library(NothinFancy STATIC "src/Engine.cpp" "src/include/nf.h" "src/pch.h" "src/util.h" "src/util/log.h" "src/util/log.cpp" "src/include/nf/config.h" "src/util/util.cpp" "src/util/file.h" "src/util/file.cpp" "src/client/Client.h" "src/client/Client.cpp" "src/client/Window.h" "src/client/Window.cpp" "src/client/render/RenderEngine.h" "src/client/render/RenderEngine.cpp" "src/client/render/ShaderModule.h" "src/client/render/ShaderModule.cpp" "src/client/render/GraphicsResource.h" "src/client/render/Buffer.h" "src/client/render/Buffer.cpp" "src/client/render/VideoMemoryAllocator.h" "src/client/render/VideoMemoryAllocator.cpp" "src/client/render/Image.h" "src/client/render/Image.cpp" "src/client/render/CommandPool.h" "src/client/render/CommandPool.cpp")
|
||||||
|
|
||||||
|
# Use C++20
|
||||||
|
set_property(TARGET NothinFancy PROPERTY CXX_STANDARD 20)
|
||||||
|
|
||||||
|
# Additional include directories
|
||||||
|
target_include_directories(NothinFancy PUBLIC "src" "src/include" "dep/include")
|
||||||
|
|
||||||
|
# Use precompiled header
|
||||||
|
target_precompile_headers(NothinFancy PUBLIC "src/pch.h")
|
||||||
|
|
||||||
|
# Link libraries
|
||||||
|
target_link_libraries(NothinFancy "$ENV{VULKAN_SDK}/Lib/vulkan-1.lib")
|
||||||
|
target_include_directories(NothinFancy PUBLIC "$ENV{VULKAN_SDK}/Include")
|
||||||
|
|
||||||
|
# Generate version.h
|
||||||
|
find_package(Git)
|
||||||
|
execute_process(COMMAND ${GIT_EXECUTABLE} describe OUTPUT_VARIABLE NFVERSION OUTPUT_STRIP_TRAILING_WHITESPACE)
|
||||||
|
configure_file(src/version.h.in version.h)
|
||||||
|
target_include_directories(NothinFancy PUBLIC "${PROJECT_BINARY_DIR}/NothinFancy")
|
||||||
|
|
||||||
|
# Compile shaders
|
||||||
|
set(GLSLANG "$ENV{VULKAN_SDK}/Bin/glslang.exe")
|
||||||
|
set(SHADER_OUTPUT_DIRECTORY "${PROJECT_BINARY_DIR}/NothinFancy/shaders")
|
||||||
|
file(GLOB_RECURSE SHADER_SOURCES "res/shaders/*.glsl")
|
||||||
|
foreach(SHADER_SOURCE ${SHADER_SOURCES})
|
||||||
|
get_filename_component(FILENAME ${SHADER_SOURCE} NAME)
|
||||||
|
set(SHADER_BINARY "${SHADER_OUTPUT_DIRECTORY}/${FILENAME}.spv")
|
||||||
|
add_custom_command(
|
||||||
|
OUTPUT ${SHADER_BINARY}
|
||||||
|
COMMAND ${CMAKE_COMMAND} -E make_directory "${SHADER_OUTPUT_DIRECTORY}"
|
||||||
|
COMMAND ${GLSLANG} -V ${SHADER_SOURCE} -o ${SHADER_BINARY}
|
||||||
|
DEPENDS ${SHADER_SOURCE}
|
||||||
|
)
|
||||||
|
list(APPEND SHADER_BINARIES ${SHADER_BINARY})
|
||||||
|
endforeach(SHADER_SOURCE)
|
||||||
|
|
||||||
|
add_custom_target(
|
||||||
|
Shaders
|
||||||
|
DEPENDS ${SHADER_BINARIES}
|
||||||
|
)
|
||||||
|
|
||||||
|
add_dependencies(NothinFancy Shaders)
|
7988
NothinFancy/dep/include/stb_image.h
Normal file
7988
NothinFancy/dep/include/stb_image.h
Normal file
File diff suppressed because it is too large
Load Diff
14
NothinFancy/res/shaders/output.frag.glsl
Normal file
14
NothinFancy/res/shaders/output.frag.glsl
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
#version 450
|
||||||
|
|
||||||
|
// Inputs
|
||||||
|
layout(location = 0) in vec2 inTextureCoordinates;
|
||||||
|
|
||||||
|
// Outputs
|
||||||
|
layout(location = 0) out vec4 outColor;
|
||||||
|
|
||||||
|
// Uniforms
|
||||||
|
layout(binding = 1) uniform sampler2D image;
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
outColor = texture(image, inTextureCoordinates);
|
||||||
|
}
|
18
NothinFancy/res/shaders/output.vert.glsl
Normal file
18
NothinFancy/res/shaders/output.vert.glsl
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
#version 450
|
||||||
|
|
||||||
|
// Inputs
|
||||||
|
layout(location = 0) in vec3 inPosition;
|
||||||
|
layout(location = 1) in vec2 inTextureCoordinates;
|
||||||
|
|
||||||
|
// Outputs
|
||||||
|
layout(location = 0) out vec2 outTextureCoordinates;
|
||||||
|
|
||||||
|
// Uniforms
|
||||||
|
layout(binding = 0) uniform MVPMatrixUniformBufferObject {
|
||||||
|
mat4 mvp;
|
||||||
|
} mvpUBO;
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
outTextureCoordinates = inTextureCoordinates;
|
||||||
|
gl_Position = mvpUBO.mvp * vec4(inPosition, 1.0);
|
||||||
|
}
|
34
NothinFancy/src/Engine.cpp
Normal file
34
NothinFancy/src/Engine.cpp
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
// NF startup
|
||||||
|
#include "pch.h"
|
||||||
|
|
||||||
|
#include "nf/config.h"
|
||||||
|
#include "version.h"
|
||||||
|
#include "util.h"
|
||||||
|
#include "client/Client.h"
|
||||||
|
|
||||||
|
// Enable visual styles for error boxes
|
||||||
|
#pragma comment(linker,"\"/manifestdependency:type='win32' \
|
||||||
|
name='Microsoft.Windows.Common-Controls' version='6.0.0.0' \
|
||||||
|
processorArchitecture='*' publicKeyToken='6595b64144ccf1df' language='*'\"")
|
||||||
|
|
||||||
|
namespace nf {
|
||||||
|
void runEngine(ClientConfig config) {
|
||||||
|
std::string engineStr = std::format("Nothin' Fancy {}", NFVERSION);
|
||||||
|
std::string gameStr = std::format("{} {}", config.appName, config.appVersion);
|
||||||
|
NFLog(engineStr);
|
||||||
|
NFLog(std::format("Starting {}", gameStr));
|
||||||
|
|
||||||
|
#ifdef _DEBUG
|
||||||
|
SetThreadDescription(GetCurrentThread(), L"NF Main Thread");
|
||||||
|
SetConsoleTitle(std::format("{} Debug Console - {}", engineStr, gameStr).c_str());
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// Start client
|
||||||
|
{
|
||||||
|
client::Client client(config);
|
||||||
|
client.run();
|
||||||
|
}
|
||||||
|
|
||||||
|
NFLog("Engine shutdown");
|
||||||
|
}
|
||||||
|
}
|
49
NothinFancy/src/client/Client.cpp
Normal file
49
NothinFancy/src/client/Client.cpp
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
// NF Client class implementation
|
||||||
|
#include "pch.h"
|
||||||
|
|
||||||
|
#include "Client.h"
|
||||||
|
#include "util.h"
|
||||||
|
|
||||||
|
namespace nf::client {
|
||||||
|
Client::Client(ClientConfig config)
|
||||||
|
: m_running(true)
|
||||||
|
, m_config(config)
|
||||||
|
, m_renderEngine()
|
||||||
|
{}
|
||||||
|
|
||||||
|
void Client::run() {
|
||||||
|
// Create window on input thread and pass up a pointer for the renderer
|
||||||
|
std::promise<std::shared_ptr<Window>> windowPromise;
|
||||||
|
auto windowFuture = windowPromise.get_future();
|
||||||
|
std::thread inputThread(&Client::runInputThread, this, std::move(windowPromise));
|
||||||
|
m_renderEngine = std::make_unique<render::RenderEngine>(std::move(windowFuture.get()), m_config.display);
|
||||||
|
|
||||||
|
auto fpsClock1 = std::chrono::high_resolution_clock::now(), fpsClock2 = fpsClock1;
|
||||||
|
unsigned int frame = 0;
|
||||||
|
while (m_running) {
|
||||||
|
m_renderEngine->doFrame();
|
||||||
|
frame++;
|
||||||
|
fpsClock2 = std::chrono::high_resolution_clock::now();
|
||||||
|
std::chrono::duration<double> duration = fpsClock2 - fpsClock1;
|
||||||
|
if (duration.count() >= 1.0) {
|
||||||
|
NFLog(std::format("FPS: {}", frame));
|
||||||
|
frame = 0;
|
||||||
|
fpsClock1 = std::chrono::high_resolution_clock::now();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
inputThread.join();
|
||||||
|
}
|
||||||
|
|
||||||
|
void Client::runInputThread(std::promise<std::shared_ptr<Window>> windowPromise) {
|
||||||
|
#ifdef _DEBUG
|
||||||
|
SetThreadDescription(GetCurrentThread(), L"NF Input Thread");
|
||||||
|
#endif
|
||||||
|
|
||||||
|
std::shared_ptr<Window> window = std::make_shared<Window>(m_config.appName);
|
||||||
|
windowPromise.set_value(window);
|
||||||
|
|
||||||
|
window->runLoop();
|
||||||
|
m_running = false;
|
||||||
|
}
|
||||||
|
}
|
21
NothinFancy/src/client/Client.h
Normal file
21
NothinFancy/src/client/Client.h
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
// NF Client class header
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "nf/config.h"
|
||||||
|
#include "Window.h"
|
||||||
|
#include "render/RenderEngine.h"
|
||||||
|
|
||||||
|
namespace nf::client {
|
||||||
|
class Client {
|
||||||
|
public:
|
||||||
|
Client(ClientConfig config);
|
||||||
|
|
||||||
|
void run();
|
||||||
|
private:
|
||||||
|
void runInputThread(std::promise<std::shared_ptr<Window>> windowPromise);
|
||||||
|
|
||||||
|
bool m_running;
|
||||||
|
ClientConfig m_config;
|
||||||
|
std::unique_ptr<render::RenderEngine> m_renderEngine;
|
||||||
|
};
|
||||||
|
}
|
139
NothinFancy/src/client/Window.cpp
Normal file
139
NothinFancy/src/client/Window.cpp
Normal file
@ -0,0 +1,139 @@
|
|||||||
|
// Window class implementation
|
||||||
|
#include "pch.h"
|
||||||
|
|
||||||
|
#include "Window.h"
|
||||||
|
#include "util.h"
|
||||||
|
|
||||||
|
namespace nf::client {
|
||||||
|
Window::Window(const char* windowTitle)
|
||||||
|
: m_handle(nullptr)
|
||||||
|
, m_wndClassName("NothinFancyWindow")
|
||||||
|
, m_styleWindowed(WS_CAPTION | WS_SYSMENU | WS_MINIMIZEBOX)
|
||||||
|
, m_styleFullscreen(WS_POPUP)
|
||||||
|
, m_currentWidth()
|
||||||
|
, m_currentHeight()
|
||||||
|
, m_active(true)
|
||||||
|
{
|
||||||
|
NFLog("Creating window");
|
||||||
|
|
||||||
|
// Disable automatic DPI upscaling
|
||||||
|
SetProcessDpiAwarenessContext(DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2);
|
||||||
|
|
||||||
|
registerWindowClass();
|
||||||
|
|
||||||
|
m_handle = CreateWindow(m_wndClassName, windowTitle, NULL, 0, 0, 0, 0, nullptr, nullptr, nullptr, this);
|
||||||
|
}
|
||||||
|
|
||||||
|
HWND Window::getHandle() const {
|
||||||
|
return m_handle;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Window::setDisplay(DisplayConfig& config) {
|
||||||
|
NFLog("Setting window display");
|
||||||
|
bool wasShown = IsWindowVisible(m_handle);
|
||||||
|
show(false);
|
||||||
|
|
||||||
|
// TODO: Only use "active" monitor when starting windowed
|
||||||
|
POINT cursor = {};
|
||||||
|
GetCursorPos(&cursor);
|
||||||
|
MONITORINFO mi = {};
|
||||||
|
mi.cbSize = sizeof(mi);
|
||||||
|
GetMonitorInfo(MonitorFromPoint(cursor, MONITOR_DEFAULTTONEAREST), &mi);
|
||||||
|
int monitorX = mi.rcMonitor.left, monitorY = mi.rcMonitor.top;
|
||||||
|
int monitorWidth = mi.rcMonitor.right - monitorX, monitorHeight = mi.rcMonitor.bottom - monitorY;
|
||||||
|
int windowX = 0, windowY = 0;
|
||||||
|
unsigned int windowWidth = 0, windowHeight = 0;
|
||||||
|
|
||||||
|
switch (config.mode) {
|
||||||
|
case DisplayMode::Windowed: {
|
||||||
|
SetWindowLongPtr(m_handle, GWL_STYLE, m_styleWindowed);
|
||||||
|
m_currentWidth = config.width, m_currentHeight = config.height;
|
||||||
|
windowX = monitorX + (monitorWidth / 2) - (m_currentWidth / 2), windowY = monitorY + (monitorHeight / 2) - (m_currentHeight / 2);
|
||||||
|
SIZE windowSize = getWindowSize();
|
||||||
|
windowWidth = windowSize.cx, windowHeight = windowSize.cy;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case DisplayMode::Fullscreen: {
|
||||||
|
SetWindowLongPtr(m_handle, GWL_STYLE, m_styleFullscreen);
|
||||||
|
windowX = monitorX, windowY = monitorY;
|
||||||
|
windowWidth = monitorWidth, windowHeight = monitorHeight;
|
||||||
|
m_currentWidth = windowWidth, m_currentHeight = windowHeight;
|
||||||
|
config.width = windowWidth, config.height = windowHeight;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
SetWindowPos(m_handle, nullptr, windowX, windowY, windowWidth, windowHeight, SWP_NOZORDER | SWP_FRAMECHANGED);
|
||||||
|
|
||||||
|
show(wasShown);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Window::runLoop() {
|
||||||
|
// At some point, the window started to show unselected at first.
|
||||||
|
SetFocus(m_handle);
|
||||||
|
MSG msg = {};
|
||||||
|
while (GetMessage(&msg, nullptr, NULL, NULL)) {
|
||||||
|
TranslateMessage(&msg);
|
||||||
|
DispatchMessage(&msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
m_active = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Window::show(bool show) {
|
||||||
|
ShowWindow(m_handle, show ? SW_SHOW : SW_HIDE);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Window::isActive() const {
|
||||||
|
return m_active;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Window::registerWindowClass() {
|
||||||
|
WNDCLASS wc = {};
|
||||||
|
wc.lpszClassName = m_wndClassName;
|
||||||
|
wc.hCursor = LoadCursor(nullptr, IDC_ARROW);
|
||||||
|
wc.lpfnWndProc = wndProc;
|
||||||
|
RegisterClass(&wc);
|
||||||
|
}
|
||||||
|
|
||||||
|
LRESULT CALLBACK Window::wndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam) {
|
||||||
|
static Window* wnd = nullptr;
|
||||||
|
|
||||||
|
switch (msg) {
|
||||||
|
case WM_CREATE:
|
||||||
|
wnd = reinterpret_cast<Window*>(reinterpret_cast<CREATESTRUCT*>(lParam)->lpCreateParams);
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
case WM_DPICHANGED: {
|
||||||
|
// Prevents automatic window resize on DPI change (don't apply to fullscreen)
|
||||||
|
if (GetWindowLongPtr(hWnd, GWL_STYLE) == wnd->m_styleFullscreen)
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
SIZE windowSize = wnd->getWindowSize();
|
||||||
|
SetWindowPos(hWnd, nullptr, 0, 0, windowSize.cx, windowSize.cy, SWP_NOZORDER | SWP_NOMOVE);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
case WM_CLOSE:
|
||||||
|
PostQuitMessage(0);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
return DefWindowProc(hWnd, msg, wParam, lParam);
|
||||||
|
}
|
||||||
|
|
||||||
|
SIZE Window::getWindowSize() {
|
||||||
|
RECT cli = {};
|
||||||
|
cli.right = m_currentWidth;
|
||||||
|
cli.bottom = m_currentHeight;
|
||||||
|
AdjustWindowRectExForDpi(&cli, m_styleWindowed, FALSE, NULL, GetDpiForWindow(m_handle));
|
||||||
|
int width = cli.right - cli.left, height = cli.bottom - cli.top;
|
||||||
|
return { width, height };
|
||||||
|
}
|
||||||
|
|
||||||
|
Window::~Window() {
|
||||||
|
DestroyWindow(m_handle);
|
||||||
|
NFLog("Window destroyed");
|
||||||
|
}
|
||||||
|
}
|
35
NothinFancy/src/client/Window.h
Normal file
35
NothinFancy/src/client/Window.h
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
// Window class header
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "nf/config.h"
|
||||||
|
|
||||||
|
#include <Windows.h>
|
||||||
|
|
||||||
|
namespace nf::client {
|
||||||
|
class Window {
|
||||||
|
public:
|
||||||
|
Window(const char* windowTitle);
|
||||||
|
|
||||||
|
HWND getHandle() const;
|
||||||
|
void setDisplay(DisplayConfig& config);
|
||||||
|
void runLoop();
|
||||||
|
void show(bool show = true);
|
||||||
|
bool isActive() const;
|
||||||
|
|
||||||
|
~Window();
|
||||||
|
private:
|
||||||
|
void registerWindowClass();
|
||||||
|
static LRESULT CALLBACK wndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam);
|
||||||
|
SIZE getWindowSize();
|
||||||
|
|
||||||
|
HWND m_handle;
|
||||||
|
|
||||||
|
const char* m_wndClassName;
|
||||||
|
const DWORD m_styleWindowed;
|
||||||
|
const DWORD m_styleFullscreen;
|
||||||
|
|
||||||
|
unsigned int m_currentWidth, m_currentHeight;
|
||||||
|
|
||||||
|
bool m_active;
|
||||||
|
};
|
||||||
|
}
|
91
NothinFancy/src/client/render/Buffer.cpp
Normal file
91
NothinFancy/src/client/render/Buffer.cpp
Normal file
@ -0,0 +1,91 @@
|
|||||||
|
// Buffer class implementation
|
||||||
|
#include "pch.h"
|
||||||
|
|
||||||
|
#include "Buffer.h"
|
||||||
|
#include "util.h"
|
||||||
|
|
||||||
|
namespace nf::client::render {
|
||||||
|
Buffer::Buffer(BufferType type, const VkDevice& device, VideoMemoryAllocator& allocator, const CommandPool& commandPool, void* bufferData, size_t bufferSize)
|
||||||
|
: GraphicsResource(device)
|
||||||
|
, m_allocator(allocator)
|
||||||
|
, m_handle()
|
||||||
|
, m_allocation()
|
||||||
|
, m_indexCount()
|
||||||
|
{
|
||||||
|
if (type == BufferType::Staging) {
|
||||||
|
createBuffer(bufferSize, VK_BUFFER_USAGE_TRANSFER_SRC_BIT, m_handle);
|
||||||
|
m_allocator.allocateForBuffer(m_handle, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, m_allocation);
|
||||||
|
if (m_allocation.mappedMemoryPointer)
|
||||||
|
memcpy(m_allocation.mappedMemoryPointer, bufferData, bufferSize);
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (type == BufferType::Uniform) {
|
||||||
|
createBuffer(bufferSize, VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT, m_handle);
|
||||||
|
m_allocator.allocateForBuffer(m_handle, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, m_allocation);
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Buffer stagingBuffer(BufferType::Staging, m_device, m_allocator, commandPool, bufferData, bufferSize);
|
||||||
|
|
||||||
|
VkBufferUsageFlags mainUsage = NULL;
|
||||||
|
|
||||||
|
switch (type) {
|
||||||
|
case BufferType::Vertex:
|
||||||
|
mainUsage = VK_BUFFER_USAGE_VERTEX_BUFFER_BIT;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case BufferType::Index:
|
||||||
|
mainUsage = VK_BUFFER_USAGE_INDEX_BUFFER_BIT;
|
||||||
|
m_indexCount = bufferSize / sizeof(uint32_t);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
createBuffer(bufferSize, VK_BUFFER_USAGE_TRANSFER_DST_BIT | mainUsage, m_handle);
|
||||||
|
m_allocator.allocateForBuffer(m_handle, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, m_allocation);
|
||||||
|
|
||||||
|
copyBuffer(stagingBuffer.getHandle(), m_handle, bufferSize, commandPool);
|
||||||
|
}
|
||||||
|
|
||||||
|
const VkBuffer& Buffer::getHandle() const {
|
||||||
|
return m_handle;
|
||||||
|
}
|
||||||
|
|
||||||
|
void* Buffer::getPointer() const {
|
||||||
|
return m_allocation.mappedMemoryPointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t Buffer::getIndicesCount() const {
|
||||||
|
return m_indexCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Buffer::createBuffer(VkDeviceSize size, VkBufferUsageFlags usage, VkBuffer& buffer) {
|
||||||
|
VkBufferCreateInfo bufferCI = { VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO };
|
||||||
|
bufferCI.size = size;
|
||||||
|
bufferCI.usage = usage;
|
||||||
|
|
||||||
|
if (vkCreateBuffer(m_device, &bufferCI, nullptr, &buffer) != VK_SUCCESS)
|
||||||
|
NFError("Could not create vertex buffer.");
|
||||||
|
}
|
||||||
|
|
||||||
|
void Buffer::copyBuffer(VkBuffer bufferSource, VkBuffer bufferDestination, VkDeviceSize size, const CommandPool& commandPool) {
|
||||||
|
VkCommandBuffer commandBuffer = commandPool.beginOneTimeExecution();
|
||||||
|
|
||||||
|
VkBufferCopy bufferCopyRegion = {};
|
||||||
|
bufferCopyRegion.size = size;
|
||||||
|
vkCmdCopyBuffer(commandBuffer, bufferSource, bufferDestination, 1, &bufferCopyRegion);
|
||||||
|
|
||||||
|
commandPool.endOneTimeExecution(commandBuffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Buffer::destroyBuffer(VkBuffer buffer, VideoMemoryAllocation& allocation) {
|
||||||
|
m_allocator.deallocate(allocation);
|
||||||
|
vkDestroyBuffer(m_device, buffer, nullptr);
|
||||||
|
}
|
||||||
|
|
||||||
|
Buffer::~Buffer() {
|
||||||
|
destroyBuffer(m_handle, m_allocation);
|
||||||
|
}
|
||||||
|
}
|
37
NothinFancy/src/client/render/Buffer.h
Normal file
37
NothinFancy/src/client/render/Buffer.h
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
// Buffer class header
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "GraphicsResource.h"
|
||||||
|
#include "VideoMemoryAllocator.h"
|
||||||
|
#include "CommandPool.h"
|
||||||
|
|
||||||
|
namespace nf::client::render {
|
||||||
|
enum class BufferType {
|
||||||
|
Staging,
|
||||||
|
Vertex,
|
||||||
|
Index,
|
||||||
|
Uniform
|
||||||
|
};
|
||||||
|
|
||||||
|
class Buffer : GraphicsResource {
|
||||||
|
public:
|
||||||
|
Buffer(BufferType type, const VkDevice& device, VideoMemoryAllocator& allocator, const CommandPool& commandPool, void* bufferData, size_t bufferSize);
|
||||||
|
|
||||||
|
const VkBuffer& getHandle() const;
|
||||||
|
void* getPointer() const;
|
||||||
|
uint32_t getIndicesCount() const;
|
||||||
|
|
||||||
|
~Buffer();
|
||||||
|
private:
|
||||||
|
void createBuffer(VkDeviceSize size, VkBufferUsageFlags usage, VkBuffer& buffer);
|
||||||
|
void copyBuffer(VkBuffer sourceBuffer, VkBuffer destinationBuffer, VkDeviceSize size, const CommandPool& commandPool);
|
||||||
|
|
||||||
|
void destroyBuffer(VkBuffer buffer, VideoMemoryAllocation& allocation);
|
||||||
|
|
||||||
|
VideoMemoryAllocator& m_allocator;
|
||||||
|
|
||||||
|
VkBuffer m_handle;
|
||||||
|
VideoMemoryAllocation m_allocation;
|
||||||
|
uint32_t m_indexCount;
|
||||||
|
};
|
||||||
|
}
|
61
NothinFancy/src/client/render/CommandPool.cpp
Normal file
61
NothinFancy/src/client/render/CommandPool.cpp
Normal file
@ -0,0 +1,61 @@
|
|||||||
|
// CommandPool class implementation
|
||||||
|
#include "pch.h"
|
||||||
|
|
||||||
|
#include "CommandPool.h"
|
||||||
|
#include "util.h"
|
||||||
|
|
||||||
|
namespace nf::client::render {
|
||||||
|
CommandPool::CommandPool(const VkDevice& device, const VkQueue& queue, uint32_t queueFamilyIndex)
|
||||||
|
: GraphicsResource(device)
|
||||||
|
, m_queue(queue)
|
||||||
|
, m_handle()
|
||||||
|
{
|
||||||
|
VkCommandPoolCreateInfo commandPoolCI = { VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO };
|
||||||
|
commandPoolCI.flags = VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT;
|
||||||
|
commandPoolCI.queueFamilyIndex = queueFamilyIndex;
|
||||||
|
|
||||||
|
if (vkCreateCommandPool(m_device, &commandPoolCI, nullptr, &m_handle) != VK_SUCCESS)
|
||||||
|
NFError("Could not create command pool.");
|
||||||
|
}
|
||||||
|
|
||||||
|
VkCommandBuffer CommandPool::allocateCommandBuffer() const {
|
||||||
|
VkCommandBuffer commandBuffer = nullptr;
|
||||||
|
|
||||||
|
VkCommandBufferAllocateInfo commandBufferAI = { VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO };
|
||||||
|
commandBufferAI.commandPool = m_handle;
|
||||||
|
commandBufferAI.commandBufferCount = 1;
|
||||||
|
|
||||||
|
if (vkAllocateCommandBuffers(m_device, &commandBufferAI, &commandBuffer) != VK_SUCCESS)
|
||||||
|
NFError("Could not create command buffer.");
|
||||||
|
|
||||||
|
return commandBuffer;
|
||||||
|
}
|
||||||
|
|
||||||
|
VkCommandBuffer CommandPool::beginOneTimeExecution() const {
|
||||||
|
VkCommandBuffer commandBuffer = allocateCommandBuffer();
|
||||||
|
|
||||||
|
VkCommandBufferBeginInfo beginInfo = { VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO };
|
||||||
|
beginInfo.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT;
|
||||||
|
|
||||||
|
vkBeginCommandBuffer(commandBuffer, &beginInfo);
|
||||||
|
|
||||||
|
return commandBuffer;
|
||||||
|
}
|
||||||
|
|
||||||
|
void CommandPool::endOneTimeExecution(VkCommandBuffer commandBuffer) const {
|
||||||
|
vkEndCommandBuffer(commandBuffer);
|
||||||
|
|
||||||
|
VkSubmitInfo submitInfo = { VK_STRUCTURE_TYPE_SUBMIT_INFO };
|
||||||
|
submitInfo.commandBufferCount = 1;
|
||||||
|
submitInfo.pCommandBuffers = &commandBuffer;
|
||||||
|
|
||||||
|
vkQueueSubmit(m_queue, 1, &submitInfo, nullptr);
|
||||||
|
vkQueueWaitIdle(m_queue);
|
||||||
|
|
||||||
|
vkFreeCommandBuffers(m_device, m_handle, 1, &commandBuffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
CommandPool::~CommandPool() {
|
||||||
|
vkDestroyCommandPool(m_device, m_handle, nullptr);
|
||||||
|
}
|
||||||
|
}
|
22
NothinFancy/src/client/render/CommandPool.h
Normal file
22
NothinFancy/src/client/render/CommandPool.h
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
// CommandPool class header
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "GraphicsResource.h"
|
||||||
|
|
||||||
|
namespace nf::client::render {
|
||||||
|
class CommandPool : GraphicsResource {
|
||||||
|
public:
|
||||||
|
CommandPool(const VkDevice& device, const VkQueue& queue, uint32_t queueFamilyIndex);
|
||||||
|
|
||||||
|
VkCommandBuffer allocateCommandBuffer() const;
|
||||||
|
|
||||||
|
VkCommandBuffer beginOneTimeExecution() const;
|
||||||
|
void endOneTimeExecution(VkCommandBuffer commandBuffer) const;
|
||||||
|
|
||||||
|
~CommandPool();
|
||||||
|
private:
|
||||||
|
const VkQueue& m_queue;
|
||||||
|
|
||||||
|
VkCommandPool m_handle;
|
||||||
|
};
|
||||||
|
}
|
14
NothinFancy/src/client/render/GraphicsResource.h
Normal file
14
NothinFancy/src/client/render/GraphicsResource.h
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
// Vulkan resource base class header
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
namespace nf::client::render {
|
||||||
|
class GraphicsResource {
|
||||||
|
public:
|
||||||
|
GraphicsResource(const VkDevice& device)
|
||||||
|
: m_device(device)
|
||||||
|
{}
|
||||||
|
|
||||||
|
protected:
|
||||||
|
const VkDevice& m_device;
|
||||||
|
};
|
||||||
|
}
|
131
NothinFancy/src/client/render/Image.cpp
Normal file
131
NothinFancy/src/client/render/Image.cpp
Normal file
@ -0,0 +1,131 @@
|
|||||||
|
// Image class implementation
|
||||||
|
#include "pch.h"
|
||||||
|
|
||||||
|
#include "Image.h"
|
||||||
|
#include "util.h"
|
||||||
|
#include "Buffer.h"
|
||||||
|
|
||||||
|
#define STB_IMAGE_IMPLEMENTATION
|
||||||
|
#include "stb_image.h"
|
||||||
|
|
||||||
|
namespace nf::client::render {
|
||||||
|
Image::Image(ImageType type, const VkDevice& device, VideoMemoryAllocator& allocator, const CommandPool& commandPool, const std::string& imageData, VkExtent2D attachmentExtent)
|
||||||
|
: GraphicsResource(device)
|
||||||
|
, m_allocator(allocator)
|
||||||
|
, m_handle()
|
||||||
|
, m_allocation()
|
||||||
|
, m_view()
|
||||||
|
{
|
||||||
|
VkFormat imageFormat = VK_FORMAT_UNDEFINED;
|
||||||
|
|
||||||
|
switch (type) {
|
||||||
|
case ImageType::Texture:
|
||||||
|
imageFormat = VK_FORMAT_R8G8B8A8_SRGB;
|
||||||
|
createTextureImage(imageFormat, commandPool, imageData);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case ImageType::DepthAttachment:
|
||||||
|
imageFormat = VK_FORMAT_D32_SFLOAT;
|
||||||
|
createImage(imageFormat, attachmentExtent, VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
createImageView(imageFormat);
|
||||||
|
}
|
||||||
|
|
||||||
|
const VkImageView& Image::getView() const {
|
||||||
|
return m_view;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Image::createTextureImage(VkFormat imageFormat, const CommandPool& commandPool, const std::string& imageData) {
|
||||||
|
int imageWidth = 0, imageHeight = 0, numChannels = 0;
|
||||||
|
stbi_uc* rawImageData = stbi_load_from_memory(reinterpret_cast<const stbi_uc*>(imageData.data()), imageData.size(), &imageWidth, &imageHeight, &numChannels, STBI_rgb_alpha);
|
||||||
|
|
||||||
|
VkDeviceSize imageSize = static_cast<VkDeviceSize>(imageWidth) * imageHeight * 4;
|
||||||
|
|
||||||
|
Buffer stagingBuffer(BufferType::Staging, m_device, m_allocator, commandPool, rawImageData, imageSize);
|
||||||
|
|
||||||
|
stbi_image_free(rawImageData);
|
||||||
|
|
||||||
|
VkExtent2D imageExtent = { static_cast<uint32_t>(imageWidth), static_cast<uint32_t>(imageHeight) };
|
||||||
|
createImage(imageFormat, imageExtent, VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_SAMPLED_BIT);
|
||||||
|
|
||||||
|
VkCommandBuffer commandBuffer = commandPool.beginOneTimeExecution();
|
||||||
|
|
||||||
|
// Image transition undefined -> transfer destination
|
||||||
|
VkImageMemoryBarrier imageMemoryBarrier = { {VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER} };
|
||||||
|
imageMemoryBarrier.image = m_handle;
|
||||||
|
imageMemoryBarrier.newLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL;
|
||||||
|
imageMemoryBarrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
|
||||||
|
imageMemoryBarrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
|
||||||
|
imageMemoryBarrier.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
|
||||||
|
imageMemoryBarrier.subresourceRange.levelCount = 1;
|
||||||
|
imageMemoryBarrier.subresourceRange.layerCount = 1;
|
||||||
|
imageMemoryBarrier.srcAccessMask = 0;
|
||||||
|
imageMemoryBarrier.dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
|
||||||
|
vkCmdPipelineBarrier(commandBuffer, VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, 0, 0, nullptr, 0, nullptr, 1, &imageMemoryBarrier);
|
||||||
|
|
||||||
|
commandPool.endOneTimeExecution(commandBuffer);
|
||||||
|
|
||||||
|
commandBuffer = commandPool.beginOneTimeExecution();
|
||||||
|
|
||||||
|
VkBufferImageCopy bufferImageCopyRegion = {};
|
||||||
|
bufferImageCopyRegion.imageSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
|
||||||
|
bufferImageCopyRegion.imageSubresource.layerCount = 1;
|
||||||
|
bufferImageCopyRegion.imageExtent = { static_cast<uint32_t>(imageWidth), static_cast<uint32_t>(imageHeight), 1 };
|
||||||
|
vkCmdCopyBufferToImage(commandBuffer, stagingBuffer.getHandle(), m_handle, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, &bufferImageCopyRegion);
|
||||||
|
|
||||||
|
commandPool.endOneTimeExecution(commandBuffer);
|
||||||
|
|
||||||
|
commandBuffer = commandPool.beginOneTimeExecution();
|
||||||
|
|
||||||
|
// Image transition transfer destination -> shader read only
|
||||||
|
imageMemoryBarrier.oldLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL;
|
||||||
|
imageMemoryBarrier.newLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
|
||||||
|
imageMemoryBarrier.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
|
||||||
|
imageMemoryBarrier.dstAccessMask = VK_ACCESS_SHADER_READ_BIT;
|
||||||
|
vkCmdPipelineBarrier(commandBuffer, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT, 0, 0, nullptr, 0, nullptr, 1, &imageMemoryBarrier);
|
||||||
|
|
||||||
|
commandPool.endOneTimeExecution(commandBuffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Image::createImage(VkFormat format, VkExtent2D& size, VkImageUsageFlags usage) {
|
||||||
|
VkImageCreateInfo imageCI = { VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO };
|
||||||
|
imageCI.imageType = VK_IMAGE_TYPE_2D;
|
||||||
|
imageCI.extent = { size.width, size.height, 1 };
|
||||||
|
imageCI.mipLevels = 1;
|
||||||
|
imageCI.arrayLayers = 1;
|
||||||
|
imageCI.samples = VK_SAMPLE_COUNT_1_BIT;
|
||||||
|
imageCI.format = format;
|
||||||
|
imageCI.usage = usage;
|
||||||
|
imageCI.tiling = VK_IMAGE_TILING_OPTIMAL;
|
||||||
|
|
||||||
|
if (vkCreateImage(m_device, &imageCI, nullptr, &m_handle) != VK_SUCCESS)
|
||||||
|
NFError("Could not create image.");
|
||||||
|
|
||||||
|
m_allocator.allocateForImage(m_handle, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, m_allocation);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Image::createImageView(VkFormat format) {
|
||||||
|
VkImageAspectFlags aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
|
||||||
|
if (format == VK_FORMAT_D32_SFLOAT)
|
||||||
|
aspectMask = VK_IMAGE_ASPECT_DEPTH_BIT;
|
||||||
|
|
||||||
|
VkImageViewCreateInfo imageViewCI = { VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO };
|
||||||
|
imageViewCI.image = m_handle;
|
||||||
|
imageViewCI.viewType = VK_IMAGE_VIEW_TYPE_2D;
|
||||||
|
imageViewCI.format = format;
|
||||||
|
imageViewCI.subresourceRange.aspectMask = aspectMask;
|
||||||
|
imageViewCI.subresourceRange.levelCount = 1;
|
||||||
|
imageViewCI.subresourceRange.layerCount = 1;
|
||||||
|
|
||||||
|
if (vkCreateImageView(m_device, &imageViewCI, nullptr, &m_view) != VK_SUCCESS)
|
||||||
|
NFError("Could not create image view.");
|
||||||
|
}
|
||||||
|
|
||||||
|
Image::~Image() {
|
||||||
|
vkDestroyImageView(m_device, m_view, nullptr);
|
||||||
|
m_allocator.deallocate(m_allocation);
|
||||||
|
vkDestroyImage(m_device, m_handle, nullptr);
|
||||||
|
}
|
||||||
|
}
|
32
NothinFancy/src/client/render/Image.h
Normal file
32
NothinFancy/src/client/render/Image.h
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
// Image class header
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "GraphicsResource.h"
|
||||||
|
#include "VideoMemoryAllocator.h"
|
||||||
|
#include "CommandPool.h"
|
||||||
|
|
||||||
|
namespace nf::client::render {
|
||||||
|
enum class ImageType {
|
||||||
|
Texture,
|
||||||
|
DepthAttachment
|
||||||
|
};
|
||||||
|
|
||||||
|
class Image : GraphicsResource {
|
||||||
|
public:
|
||||||
|
Image(ImageType type, const VkDevice& device, VideoMemoryAllocator& allocator, const CommandPool& commandPool, const std::string& imageData, VkExtent2D attachmentExtent = {});
|
||||||
|
|
||||||
|
const VkImageView& getView() const;
|
||||||
|
|
||||||
|
~Image();
|
||||||
|
private:
|
||||||
|
void createTextureImage(VkFormat imageFormat, const CommandPool& commandPool, const std::string& imageData);
|
||||||
|
void createImage(VkFormat imageFormat, VkExtent2D& size, VkImageUsageFlags usage);
|
||||||
|
void createImageView(VkFormat format);
|
||||||
|
|
||||||
|
VideoMemoryAllocator& m_allocator;
|
||||||
|
|
||||||
|
VkImage m_handle;
|
||||||
|
VideoMemoryAllocation m_allocation;
|
||||||
|
VkImageView m_view;
|
||||||
|
};
|
||||||
|
}
|
783
NothinFancy/src/client/render/RenderEngine.cpp
Normal file
783
NothinFancy/src/client/render/RenderEngine.cpp
Normal file
@ -0,0 +1,783 @@
|
|||||||
|
// RenderEngine class implementation
|
||||||
|
#include "pch.h"
|
||||||
|
|
||||||
|
#include "RenderEngine.h"
|
||||||
|
#include "util.h"
|
||||||
|
#include "ShaderModule.h"
|
||||||
|
|
||||||
|
namespace nf::client::render {
|
||||||
|
// Vertex layout definition
|
||||||
|
struct Vertex {
|
||||||
|
glm::vec3 position;
|
||||||
|
glm::vec2 textureCoordinates;
|
||||||
|
};
|
||||||
|
|
||||||
|
RenderEngine::RenderEngine(std::shared_ptr<Window> window, DisplayConfig display)
|
||||||
|
: m_window(window)
|
||||||
|
, m_display(display)
|
||||||
|
, m_instance()
|
||||||
|
, m_surface()
|
||||||
|
, m_physicalDevice()
|
||||||
|
, m_queueFIGraphics()
|
||||||
|
, m_queueFIPresent()
|
||||||
|
, m_device()
|
||||||
|
, m_queueGraphics()
|
||||||
|
, m_queuePresent()
|
||||||
|
, m_commandPool()
|
||||||
|
, m_commandBuffer()
|
||||||
|
, m_semaphoreImageAvailable()
|
||||||
|
, m_semaphoreRenderingDone()
|
||||||
|
, m_fenceFrameInFlight()
|
||||||
|
, m_swapchain()
|
||||||
|
, m_swapchainImageFormat()
|
||||||
|
, m_swapchainExtent()
|
||||||
|
, m_swapchainImages()
|
||||||
|
, m_swapchainImageViews()
|
||||||
|
, m_renderPassOutput()
|
||||||
|
, m_pipelineOutputDescriptorSetLayout()
|
||||||
|
, m_pipelineOutputLayout()
|
||||||
|
, m_pipelineOutputDescriptorPool()
|
||||||
|
, m_pipelineOutput()
|
||||||
|
, m_allocator()
|
||||||
|
, m_imageDepth()
|
||||||
|
, m_swapchainFramebuffers()
|
||||||
|
, m_bufferVertex()
|
||||||
|
, m_bufferIndex()
|
||||||
|
, m_bufferUniformMVP()
|
||||||
|
, m_imageTest()
|
||||||
|
, m_sampler()
|
||||||
|
, m_pipelineOutputDescriptorSet()
|
||||||
|
|
||||||
|
{
|
||||||
|
NFLog("Initializing render engine");
|
||||||
|
m_window->setDisplay(m_display);
|
||||||
|
|
||||||
|
createInstance();
|
||||||
|
createSurface();
|
||||||
|
pickPhysicalDevice();
|
||||||
|
createDevice();
|
||||||
|
createExecutionObjects();
|
||||||
|
|
||||||
|
createSwapchain();
|
||||||
|
|
||||||
|
createOutputRenderPass();
|
||||||
|
createOutputPipeline();
|
||||||
|
|
||||||
|
m_allocator = std::make_unique<VideoMemoryAllocator>(m_device, m_physicalDevice);
|
||||||
|
createSwapchainFramebuffers();
|
||||||
|
createBuffers();
|
||||||
|
createImage();
|
||||||
|
createDescriptorSet();
|
||||||
|
|
||||||
|
m_window->show();
|
||||||
|
}
|
||||||
|
|
||||||
|
void RenderEngine::createInstance() {
|
||||||
|
VkApplicationInfo appInfo = { VK_STRUCTURE_TYPE_APPLICATION_INFO };
|
||||||
|
appInfo.apiVersion = VK_API_VERSION_1_0;
|
||||||
|
|
||||||
|
VkInstanceCreateInfo instanceCI = { VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO };
|
||||||
|
instanceCI.pApplicationInfo = &appInfo;
|
||||||
|
#ifdef _DEBUG
|
||||||
|
const char* validationLayerName = "VK_LAYER_KHRONOS_validation";
|
||||||
|
instanceCI.ppEnabledLayerNames = &validationLayerName;
|
||||||
|
instanceCI.enabledLayerCount = 1;
|
||||||
|
#endif
|
||||||
|
std::vector<const char*> instanceExtNames = { VK_KHR_SURFACE_EXTENSION_NAME, VK_KHR_WIN32_SURFACE_EXTENSION_NAME };
|
||||||
|
instanceCI.ppEnabledExtensionNames = instanceExtNames.data();
|
||||||
|
instanceCI.enabledExtensionCount = instanceExtNames.size();
|
||||||
|
|
||||||
|
if (vkCreateInstance(&instanceCI, nullptr, &m_instance) != VK_SUCCESS)
|
||||||
|
NFError("Could not create Vulkan instance.");
|
||||||
|
}
|
||||||
|
|
||||||
|
void RenderEngine::createSurface() {
|
||||||
|
VkWin32SurfaceCreateInfoKHR surfaceCI = { VK_STRUCTURE_TYPE_WIN32_SURFACE_CREATE_INFO_KHR };
|
||||||
|
surfaceCI.hwnd = m_window->getHandle();
|
||||||
|
|
||||||
|
if (vkCreateWin32SurfaceKHR(m_instance, &surfaceCI, nullptr, &m_surface) != VK_SUCCESS)
|
||||||
|
NFError("Could not create Vulkan surface.");
|
||||||
|
}
|
||||||
|
|
||||||
|
void RenderEngine::pickPhysicalDevice() {
|
||||||
|
// TODO: Save GPU choice in DisplayConfig
|
||||||
|
// First, get list of all GPUs
|
||||||
|
uint32_t numPhysicalDevices = 0;
|
||||||
|
vkEnumeratePhysicalDevices(m_instance, &numPhysicalDevices, nullptr);
|
||||||
|
if (numPhysicalDevices == 0)
|
||||||
|
NFError("No Vulkan GPUs found.");
|
||||||
|
|
||||||
|
std::vector<VkPhysicalDevice> physicalDevices(numPhysicalDevices);
|
||||||
|
vkEnumeratePhysicalDevices(m_instance, &numPhysicalDevices, physicalDevices.data());
|
||||||
|
|
||||||
|
// Then gather information on them and save first dedicated
|
||||||
|
std::optional<int> firstDedicatedPhysicalDeviceIndex;
|
||||||
|
std::vector<VkPhysicalDeviceProperties> physicalDeviceProperties;
|
||||||
|
physicalDeviceProperties.reserve(numPhysicalDevices);
|
||||||
|
for (int i = 0; i < numPhysicalDevices; i++) {
|
||||||
|
VkPhysicalDeviceProperties currentPhysicalDeviceProperties = {};
|
||||||
|
vkGetPhysicalDeviceProperties(physicalDevices[i], ¤tPhysicalDeviceProperties);
|
||||||
|
if (currentPhysicalDeviceProperties.deviceType == VK_PHYSICAL_DEVICE_TYPE_DISCRETE_GPU && !firstDedicatedPhysicalDeviceIndex.has_value())
|
||||||
|
firstDedicatedPhysicalDeviceIndex = i;
|
||||||
|
|
||||||
|
physicalDeviceProperties.push_back(currentPhysicalDeviceProperties);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Try dedicated first, then try every GPU in order
|
||||||
|
if (firstDedicatedPhysicalDeviceIndex.has_value()) {
|
||||||
|
int dedicatedIndex = firstDedicatedPhysicalDeviceIndex.value();
|
||||||
|
physicalDevices.insert(physicalDevices.begin(), physicalDevices[dedicatedIndex]);
|
||||||
|
physicalDevices.erase(physicalDevices.begin() + dedicatedIndex + 1);
|
||||||
|
physicalDeviceProperties.insert(physicalDeviceProperties.begin(), physicalDeviceProperties[dedicatedIndex]);
|
||||||
|
physicalDeviceProperties.erase(physicalDeviceProperties.begin() + dedicatedIndex + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
struct PhysicalDeviceQueueFamilyIndices {
|
||||||
|
uint32_t graphics, present;
|
||||||
|
};
|
||||||
|
|
||||||
|
auto getQueueFamilyIndices = [&](int currentPhysicalDeviceIndex) -> std::optional<PhysicalDeviceQueueFamilyIndices> {
|
||||||
|
uint32_t numQueueFamilies = 0;
|
||||||
|
vkGetPhysicalDeviceQueueFamilyProperties(physicalDevices[currentPhysicalDeviceIndex], &numQueueFamilies, nullptr);
|
||||||
|
std::vector<VkQueueFamilyProperties> queueFamilyProperties(numQueueFamilies);
|
||||||
|
vkGetPhysicalDeviceQueueFamilyProperties(physicalDevices[currentPhysicalDeviceIndex], &numQueueFamilies, queueFamilyProperties.data());
|
||||||
|
std::optional<uint32_t> graphicsIndex, presentIndex;
|
||||||
|
|
||||||
|
for (int i = 0; i < numQueueFamilies; i++) {
|
||||||
|
if (queueFamilyProperties[i].queueFlags & VK_QUEUE_GRAPHICS_BIT && !graphicsIndex.has_value())
|
||||||
|
graphicsIndex = i;
|
||||||
|
|
||||||
|
VkBool32 hasPresentSupport = VK_FALSE;
|
||||||
|
vkGetPhysicalDeviceSurfaceSupportKHR(physicalDevices[currentPhysicalDeviceIndex], i, m_surface, &hasPresentSupport);
|
||||||
|
if (hasPresentSupport && !presentIndex.has_value())
|
||||||
|
presentIndex = i;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (graphicsIndex.has_value() && presentIndex.has_value())
|
||||||
|
return PhysicalDeviceQueueFamilyIndices{ graphicsIndex.value(), presentIndex.value() };
|
||||||
|
|
||||||
|
return std::nullopt;
|
||||||
|
};
|
||||||
|
|
||||||
|
std::optional<PhysicalDeviceQueueFamilyIndices> chosenPhysicalDeviceIndices;
|
||||||
|
for (int i = 0; i < numPhysicalDevices; i++) {
|
||||||
|
chosenPhysicalDeviceIndices = getQueueFamilyIndices(i);
|
||||||
|
if (chosenPhysicalDeviceIndices.has_value()) {
|
||||||
|
// GPU found!
|
||||||
|
m_physicalDevice = physicalDevices[i];
|
||||||
|
m_queueFIGraphics = chosenPhysicalDeviceIndices->graphics;
|
||||||
|
m_queueFIPresent = chosenPhysicalDeviceIndices->present;
|
||||||
|
|
||||||
|
NFLog(std::format("GPU - {}", physicalDeviceProperties[i].deviceName));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (m_physicalDevice == nullptr)
|
||||||
|
NFError("No Vulkan GPUs were found to be compatible.");
|
||||||
|
}
|
||||||
|
|
||||||
|
void RenderEngine::createDevice() {
|
||||||
|
std::set<uint32_t> uniqueQueueFamilyIndices = { m_queueFIGraphics, m_queueFIPresent };
|
||||||
|
|
||||||
|
float queuePriority = 1.0f;
|
||||||
|
std::vector<VkDeviceQueueCreateInfo> queueCIs;
|
||||||
|
queueCIs.reserve(uniqueQueueFamilyIndices.size());
|
||||||
|
for (uint32_t currentQueueFamilyIndex : uniqueQueueFamilyIndices) {
|
||||||
|
VkDeviceQueueCreateInfo queueCI = { VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO };
|
||||||
|
queueCI.queueFamilyIndex = currentQueueFamilyIndex;
|
||||||
|
queueCI.queueCount = 1;
|
||||||
|
queueCI.pQueuePriorities = &queuePriority;
|
||||||
|
queueCIs.push_back(queueCI);
|
||||||
|
}
|
||||||
|
|
||||||
|
VkPhysicalDeviceFeatures features = {};
|
||||||
|
|
||||||
|
VkDeviceCreateInfo deviceCI = { VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO };
|
||||||
|
deviceCI.queueCreateInfoCount = queueCIs.size();
|
||||||
|
deviceCI.pQueueCreateInfos = queueCIs.data();
|
||||||
|
deviceCI.pEnabledFeatures = &features;
|
||||||
|
|
||||||
|
const char* swapChainExtName = VK_KHR_SWAPCHAIN_EXTENSION_NAME;
|
||||||
|
deviceCI.enabledExtensionCount = 1;
|
||||||
|
deviceCI.ppEnabledExtensionNames = &swapChainExtName;
|
||||||
|
|
||||||
|
if (vkCreateDevice(m_physicalDevice, &deviceCI, nullptr, &m_device) != VK_SUCCESS)
|
||||||
|
NFError("Could not create Vulkan device.");
|
||||||
|
|
||||||
|
vkGetDeviceQueue(m_device, m_queueFIGraphics, 0, &m_queueGraphics);
|
||||||
|
vkGetDeviceQueue(m_device, m_queueFIPresent, 0, &m_queuePresent);
|
||||||
|
}
|
||||||
|
|
||||||
|
void RenderEngine::createSwapchain() {
|
||||||
|
VkSurfaceCapabilitiesKHR surfaceCapabilities = {};
|
||||||
|
vkGetPhysicalDeviceSurfaceCapabilitiesKHR(m_physicalDevice, m_surface, &surfaceCapabilities);
|
||||||
|
|
||||||
|
uint32_t numSurfaceFormats = 0;
|
||||||
|
vkGetPhysicalDeviceSurfaceFormatsKHR(m_physicalDevice, m_surface, &numSurfaceFormats, nullptr);
|
||||||
|
if (numSurfaceFormats == 0)
|
||||||
|
NFError("No Vulkan surface formats found.");
|
||||||
|
|
||||||
|
std::vector<VkSurfaceFormatKHR> surfaceFormats(numSurfaceFormats);
|
||||||
|
vkGetPhysicalDeviceSurfaceFormatsKHR(m_physicalDevice, m_surface, &numSurfaceFormats, surfaceFormats.data());
|
||||||
|
VkSurfaceFormatKHR chosenSurfaceFormat = surfaceFormats[0];
|
||||||
|
for (int i = 0; i < surfaceFormats.size(); i++)
|
||||||
|
if (surfaceFormats[i].format == VK_FORMAT_B8G8R8A8_SRGB && surfaceFormats[i].colorSpace == VK_COLOR_SPACE_SRGB_NONLINEAR_KHR)
|
||||||
|
chosenSurfaceFormat = surfaceFormats[i];
|
||||||
|
|
||||||
|
uint32_t numPresentModes = 0;
|
||||||
|
vkGetPhysicalDeviceSurfacePresentModesKHR(m_physicalDevice, m_surface, &numPresentModes, nullptr);
|
||||||
|
if (numPresentModes == 0)
|
||||||
|
NFError("No Vulkan surface present modes found.");
|
||||||
|
|
||||||
|
std::vector<VkPresentModeKHR> presentModes(numPresentModes);
|
||||||
|
vkGetPhysicalDeviceSurfacePresentModesKHR(m_physicalDevice, m_surface, &numPresentModes, presentModes.data());
|
||||||
|
VkPresentModeKHR chosenPresentMode = VK_PRESENT_MODE_FIFO_KHR;
|
||||||
|
// TODO: IMMEDIATE isn't always available, so do something about that
|
||||||
|
/*for (int i = 0; i < presentModes.size(); i++)
|
||||||
|
if (presentModes[i] == VK_PRESENT_MODE_IMMEDIATE_KHR)
|
||||||
|
chosenPresentMode = presentModes[i];*/
|
||||||
|
|
||||||
|
m_swapchainExtent = surfaceCapabilities.currentExtent;
|
||||||
|
if (m_swapchainExtent.width == std::numeric_limits<uint32_t>::max())
|
||||||
|
m_swapchainExtent = { m_display.width, m_display.height };
|
||||||
|
|
||||||
|
uint32_t numRequestedImages = surfaceCapabilities.minImageCount + (surfaceCapabilities.minImageCount != surfaceCapabilities.maxImageCount ? 1 : 0);
|
||||||
|
|
||||||
|
VkSwapchainCreateInfoKHR swapchainCI = { VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR };
|
||||||
|
swapchainCI.surface = m_surface;
|
||||||
|
swapchainCI.minImageCount = numRequestedImages;
|
||||||
|
swapchainCI.imageFormat = m_swapchainImageFormat = chosenSurfaceFormat.format;
|
||||||
|
swapchainCI.imageColorSpace = chosenSurfaceFormat.colorSpace;
|
||||||
|
swapchainCI.imageExtent = m_swapchainExtent;
|
||||||
|
swapchainCI.imageArrayLayers = 1;
|
||||||
|
swapchainCI.imageUsage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT;
|
||||||
|
|
||||||
|
std::vector<uint32_t> queueFamilyIndices = { m_queueFIGraphics, m_queueFIPresent };
|
||||||
|
if (m_queueFIGraphics != m_queueFIPresent) {
|
||||||
|
swapchainCI.imageSharingMode = VK_SHARING_MODE_CONCURRENT;
|
||||||
|
swapchainCI.queueFamilyIndexCount = 2;
|
||||||
|
swapchainCI.pQueueFamilyIndices = queueFamilyIndices.data();
|
||||||
|
}
|
||||||
|
|
||||||
|
swapchainCI.preTransform = surfaceCapabilities.currentTransform;
|
||||||
|
swapchainCI.compositeAlpha = VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR;
|
||||||
|
swapchainCI.presentMode = chosenPresentMode;
|
||||||
|
swapchainCI.clipped = VK_TRUE;
|
||||||
|
|
||||||
|
if (vkCreateSwapchainKHR(m_device, &swapchainCI, nullptr, &m_swapchain) != VK_SUCCESS)
|
||||||
|
NFError("Could not create Vulkan swapchain.");
|
||||||
|
|
||||||
|
uint32_t numSwapchainImages = 0;
|
||||||
|
vkGetSwapchainImagesKHR(m_device, m_swapchain, &numSwapchainImages, nullptr);
|
||||||
|
m_swapchainImages.resize(numSwapchainImages);
|
||||||
|
vkGetSwapchainImagesKHR(m_device, m_swapchain, &numSwapchainImages, m_swapchainImages.data());
|
||||||
|
|
||||||
|
m_swapchainImageViews.resize(numSwapchainImages);
|
||||||
|
for (int i = 0; i < m_swapchainImageViews.size(); i++) {
|
||||||
|
VkImageViewCreateInfo imageViewCI = { VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO };
|
||||||
|
imageViewCI.image = m_swapchainImages[i];
|
||||||
|
imageViewCI.viewType = VK_IMAGE_VIEW_TYPE_2D;
|
||||||
|
imageViewCI.format = m_swapchainImageFormat;
|
||||||
|
imageViewCI.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
|
||||||
|
imageViewCI.subresourceRange.levelCount = 1;
|
||||||
|
imageViewCI.subresourceRange.layerCount = 1;
|
||||||
|
|
||||||
|
if (vkCreateImageView(m_device, &imageViewCI, nullptr, &m_swapchainImageViews[i]) != VK_SUCCESS)
|
||||||
|
NFError("Could not create Vulkan swapchain image view.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void RenderEngine::createExecutionObjects() {
|
||||||
|
m_commandPool = std::make_unique<CommandPool>(m_device, m_queueGraphics, m_queueFIGraphics);
|
||||||
|
m_commandBuffer = m_commandPool->allocateCommandBuffer();
|
||||||
|
|
||||||
|
VkSemaphoreCreateInfo semaphoreCI = { VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO };
|
||||||
|
if (vkCreateSemaphore(m_device, &semaphoreCI, nullptr, &m_semaphoreImageAvailable) != VK_SUCCESS ||
|
||||||
|
vkCreateSemaphore(m_device, &semaphoreCI, nullptr, &m_semaphoreRenderingDone) != VK_SUCCESS)
|
||||||
|
NFError("Could not create semaphore.");
|
||||||
|
|
||||||
|
VkFenceCreateInfo fenceCI = { VK_STRUCTURE_TYPE_FENCE_CREATE_INFO };
|
||||||
|
fenceCI.flags = VK_FENCE_CREATE_SIGNALED_BIT;
|
||||||
|
if (vkCreateFence(m_device, &fenceCI, nullptr, &m_fenceFrameInFlight) != VK_SUCCESS)
|
||||||
|
NFError("Could not create fence.");
|
||||||
|
}
|
||||||
|
|
||||||
|
void RenderEngine::createOutputRenderPass() {
|
||||||
|
VkAttachmentDescription attachmentDescriptions[2] = {};
|
||||||
|
|
||||||
|
// Color attachment
|
||||||
|
attachmentDescriptions[0].format = m_swapchainImageFormat;
|
||||||
|
attachmentDescriptions[0].samples = VK_SAMPLE_COUNT_1_BIT;
|
||||||
|
attachmentDescriptions[0].loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR;
|
||||||
|
attachmentDescriptions[0].storeOp = VK_ATTACHMENT_STORE_OP_STORE;
|
||||||
|
attachmentDescriptions[0].stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE;
|
||||||
|
attachmentDescriptions[0].stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
|
||||||
|
attachmentDescriptions[0].initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
|
||||||
|
attachmentDescriptions[0].finalLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR;
|
||||||
|
|
||||||
|
// Depth attachment
|
||||||
|
attachmentDescriptions[1] = attachmentDescriptions[0];
|
||||||
|
attachmentDescriptions[1].format = VK_FORMAT_D32_SFLOAT;
|
||||||
|
attachmentDescriptions[1].finalLayout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL;
|
||||||
|
|
||||||
|
VkAttachmentReference attachmentReferences[2] = {};
|
||||||
|
attachmentReferences[0].attachment = 0;
|
||||||
|
attachmentReferences[0].layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
|
||||||
|
attachmentReferences[1].attachment = 1;
|
||||||
|
attachmentReferences[1].layout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL;
|
||||||
|
|
||||||
|
VkSubpassDescription subpassDescription = {};
|
||||||
|
subpassDescription.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS;
|
||||||
|
subpassDescription.colorAttachmentCount = 1;
|
||||||
|
subpassDescription.pColorAttachments = &attachmentReferences[0];
|
||||||
|
subpassDescription.pDepthStencilAttachment = &attachmentReferences[1];
|
||||||
|
|
||||||
|
VkSubpassDependency dependency = {};
|
||||||
|
dependency.srcSubpass = VK_SUBPASS_EXTERNAL;
|
||||||
|
dependency.dstSubpass = 0;
|
||||||
|
dependency.srcStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT | VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT;
|
||||||
|
dependency.srcAccessMask = VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT;
|
||||||
|
dependency.dstStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT | VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT;
|
||||||
|
dependency.dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT | VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT;
|
||||||
|
|
||||||
|
VkRenderPassCreateInfo renderPassCI = { VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO };
|
||||||
|
renderPassCI.attachmentCount = 2;
|
||||||
|
renderPassCI.pAttachments = attachmentDescriptions;
|
||||||
|
renderPassCI.subpassCount = 1;
|
||||||
|
renderPassCI.pSubpasses = &subpassDescription;
|
||||||
|
renderPassCI.dependencyCount = 1;
|
||||||
|
renderPassCI.pDependencies = &dependency;
|
||||||
|
|
||||||
|
if (vkCreateRenderPass(m_device, &renderPassCI, nullptr, &m_renderPassOutput) != VK_SUCCESS)
|
||||||
|
NFError("Could not create Vulkan output render pass.");
|
||||||
|
}
|
||||||
|
|
||||||
|
void RenderEngine::createOutputPipeline() {
|
||||||
|
// First, create shader modules
|
||||||
|
std::string outputShaderVertexBinary, outputShaderFragmentBinary;
|
||||||
|
if (!util::readFile("shaders/output.vert.glsl.spv", outputShaderVertexBinary) || !util::readFile("shaders/output.frag.glsl.spv", outputShaderFragmentBinary))
|
||||||
|
NFError("Could not read output shader binaries.");
|
||||||
|
|
||||||
|
ShaderModule outputShaderVertexModule(m_device, outputShaderVertexBinary);
|
||||||
|
ShaderModule outputShaderFragmentModule(m_device, outputShaderFragmentBinary);
|
||||||
|
|
||||||
|
// Fill out pipeline shader stages
|
||||||
|
VkPipelineShaderStageCreateInfo outputShaderStages[] = { {VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO}, {VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO} };
|
||||||
|
outputShaderStages[0].stage = VK_SHADER_STAGE_VERTEX_BIT;
|
||||||
|
outputShaderStages[0].module = outputShaderVertexModule.getHandle();
|
||||||
|
outputShaderStages[0].pName = outputShaderStages[1].pName = "main";
|
||||||
|
outputShaderStages[1].stage = VK_SHADER_STAGE_FRAGMENT_BIT;
|
||||||
|
outputShaderStages[1].module = outputShaderFragmentModule.getHandle();
|
||||||
|
|
||||||
|
// Specify dynamic state
|
||||||
|
VkPipelineDynamicStateCreateInfo dynamicStateCI = { VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO };
|
||||||
|
std::vector<VkDynamicState> dynamicStates = { VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR };
|
||||||
|
dynamicStateCI.dynamicStateCount = dynamicStates.size();
|
||||||
|
dynamicStateCI.pDynamicStates = dynamicStates.data();
|
||||||
|
|
||||||
|
// Specify vertex input state
|
||||||
|
VkPipelineVertexInputStateCreateInfo vertexInputStateCI = { VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO };
|
||||||
|
VkVertexInputBindingDescription vertexBindingDescription = {};
|
||||||
|
vertexBindingDescription.stride = sizeof(Vertex);
|
||||||
|
VkVertexInputAttributeDescription vertexAttributeDescriptions[2] = {};
|
||||||
|
vertexAttributeDescriptions[0].location = 0;
|
||||||
|
vertexAttributeDescriptions[0].format = VK_FORMAT_R32G32B32_SFLOAT;
|
||||||
|
vertexAttributeDescriptions[0].offset = offsetof(Vertex, position);
|
||||||
|
vertexAttributeDescriptions[1].location = 1;
|
||||||
|
vertexAttributeDescriptions[1].format = VK_FORMAT_R32G32_SFLOAT;
|
||||||
|
vertexAttributeDescriptions[1].offset = offsetof(Vertex, textureCoordinates);
|
||||||
|
vertexInputStateCI.vertexBindingDescriptionCount = 1;
|
||||||
|
vertexInputStateCI.pVertexBindingDescriptions = &vertexBindingDescription;
|
||||||
|
vertexInputStateCI.vertexAttributeDescriptionCount = 2;
|
||||||
|
vertexInputStateCI.pVertexAttributeDescriptions = vertexAttributeDescriptions;
|
||||||
|
|
||||||
|
// Specify input assembly state
|
||||||
|
VkPipelineInputAssemblyStateCreateInfo inputAssemblyStateCI = { VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO };
|
||||||
|
inputAssemblyStateCI.topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST;
|
||||||
|
|
||||||
|
// Specify viewport state
|
||||||
|
VkPipelineViewportStateCreateInfo viewportStateCI = { VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO };
|
||||||
|
viewportStateCI.viewportCount = 1;
|
||||||
|
viewportStateCI.scissorCount = 1;
|
||||||
|
|
||||||
|
// Specify rasterization state
|
||||||
|
VkPipelineRasterizationStateCreateInfo rasterizationCI = { VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO };
|
||||||
|
rasterizationCI.lineWidth = 1.0;
|
||||||
|
rasterizationCI.cullMode = VK_CULL_MODE_BACK_BIT;
|
||||||
|
rasterizationCI.frontFace = VK_FRONT_FACE_COUNTER_CLOCKWISE;
|
||||||
|
|
||||||
|
// Specify multisample state
|
||||||
|
VkPipelineMultisampleStateCreateInfo multisampleStateCI = { VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO };
|
||||||
|
multisampleStateCI.rasterizationSamples = VK_SAMPLE_COUNT_1_BIT;
|
||||||
|
|
||||||
|
// Specify depth stencil state
|
||||||
|
VkPipelineDepthStencilStateCreateInfo depthStencilStateCI = { VK_STRUCTURE_TYPE_PIPELINE_DEPTH_STENCIL_STATE_CREATE_INFO };
|
||||||
|
depthStencilStateCI.depthTestEnable = VK_TRUE;
|
||||||
|
depthStencilStateCI.depthWriteEnable = VK_TRUE;
|
||||||
|
depthStencilStateCI.depthCompareOp = VK_COMPARE_OP_LESS;
|
||||||
|
|
||||||
|
// Specify color blend state
|
||||||
|
VkPipelineColorBlendAttachmentState colorBlendAttachmentState = {};
|
||||||
|
colorBlendAttachmentState.colorWriteMask = VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_G_BIT | VK_COLOR_COMPONENT_B_BIT | VK_COLOR_COMPONENT_A_BIT;
|
||||||
|
VkPipelineColorBlendStateCreateInfo colorBlendStateCI = { VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO };
|
||||||
|
colorBlendStateCI.attachmentCount = 1;
|
||||||
|
colorBlendStateCI.pAttachments = &colorBlendAttachmentState;
|
||||||
|
|
||||||
|
// Create descriptor set layout
|
||||||
|
VkDescriptorSetLayoutCreateInfo descriptorSetLayoutCI = { VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO };
|
||||||
|
VkDescriptorSetLayoutBinding layoutBindings[] = { {}, {} };
|
||||||
|
layoutBindings[0].binding = 0;
|
||||||
|
layoutBindings[0].descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER;
|
||||||
|
layoutBindings[0].descriptorCount = 1;
|
||||||
|
layoutBindings[0].stageFlags = VK_SHADER_STAGE_VERTEX_BIT;
|
||||||
|
layoutBindings[1].binding = 1;
|
||||||
|
layoutBindings[1].descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
|
||||||
|
layoutBindings[1].descriptorCount = 1;
|
||||||
|
layoutBindings[1].stageFlags = VK_SHADER_STAGE_FRAGMENT_BIT;
|
||||||
|
descriptorSetLayoutCI.bindingCount = 2;
|
||||||
|
descriptorSetLayoutCI.bindingCount = 2;
|
||||||
|
descriptorSetLayoutCI.pBindings = layoutBindings;
|
||||||
|
|
||||||
|
if (vkCreateDescriptorSetLayout(m_device, &descriptorSetLayoutCI, nullptr, &m_pipelineOutputDescriptorSetLayout) != VK_SUCCESS)
|
||||||
|
NFError("Could not create descriptor set layout.");
|
||||||
|
|
||||||
|
// Create pipeline layout
|
||||||
|
VkPipelineLayoutCreateInfo layoutCI = { VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO };
|
||||||
|
layoutCI.setLayoutCount = 1;
|
||||||
|
layoutCI.pSetLayouts = &m_pipelineOutputDescriptorSetLayout;
|
||||||
|
|
||||||
|
if (vkCreatePipelineLayout(m_device, &layoutCI, nullptr, &m_pipelineOutputLayout) != VK_SUCCESS)
|
||||||
|
NFError("Could not create pipeline layout.");
|
||||||
|
|
||||||
|
// Create descriptor pool
|
||||||
|
VkDescriptorPoolCreateInfo descriptorPoolCI = { VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO };
|
||||||
|
VkDescriptorPoolSize descriptorPoolSizes[] = { {}, {} };
|
||||||
|
descriptorPoolSizes[0].type = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER;
|
||||||
|
descriptorPoolSizes[0].descriptorCount = 1;
|
||||||
|
descriptorPoolSizes[1].type = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
|
||||||
|
descriptorPoolSizes[1].descriptorCount = 1;
|
||||||
|
descriptorPoolCI.poolSizeCount = 2;
|
||||||
|
descriptorPoolCI.pPoolSizes = descriptorPoolSizes;
|
||||||
|
descriptorPoolCI.maxSets = 1;
|
||||||
|
|
||||||
|
if (vkCreateDescriptorPool(m_device, &descriptorPoolCI, nullptr, &m_pipelineOutputDescriptorPool) != VK_SUCCESS)
|
||||||
|
NFError("Could not create descriptor pool.");
|
||||||
|
|
||||||
|
// And finally put it all together
|
||||||
|
VkGraphicsPipelineCreateInfo pipelineCI = { VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO };
|
||||||
|
pipelineCI.stageCount = 2;
|
||||||
|
pipelineCI.pStages = outputShaderStages;
|
||||||
|
pipelineCI.pVertexInputState = &vertexInputStateCI;
|
||||||
|
pipelineCI.pInputAssemblyState = &inputAssemblyStateCI;
|
||||||
|
pipelineCI.pViewportState = &viewportStateCI;
|
||||||
|
pipelineCI.pRasterizationState = &rasterizationCI;
|
||||||
|
pipelineCI.pMultisampleState = &multisampleStateCI;
|
||||||
|
pipelineCI.pDepthStencilState = &depthStencilStateCI;
|
||||||
|
pipelineCI.pColorBlendState = &colorBlendStateCI;
|
||||||
|
pipelineCI.pDynamicState = &dynamicStateCI;
|
||||||
|
pipelineCI.layout = m_pipelineOutputLayout;
|
||||||
|
pipelineCI.renderPass = m_renderPassOutput;
|
||||||
|
|
||||||
|
if (vkCreateGraphicsPipelines(m_device, nullptr, 1, &pipelineCI, nullptr, &m_pipelineOutput) != VK_SUCCESS)
|
||||||
|
NFError("Could not create graphics pipeline.");
|
||||||
|
}
|
||||||
|
|
||||||
|
void RenderEngine::createSwapchainFramebuffers() {
|
||||||
|
// TODO: Won't need these forever
|
||||||
|
|
||||||
|
// Create depth buffer first
|
||||||
|
m_imageDepth = std::make_unique<Image>(ImageType::DepthAttachment, m_device, *m_allocator, *m_commandPool, std::string(), m_swapchainExtent);
|
||||||
|
|
||||||
|
m_swapchainFramebuffers.resize(m_swapchainImageViews.size());
|
||||||
|
for (int i = 0; i < m_swapchainImageViews.size(); i++) {
|
||||||
|
VkFramebufferCreateInfo framebufferCI = { VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO };
|
||||||
|
framebufferCI.renderPass = m_renderPassOutput;
|
||||||
|
VkImageView views[] = { m_swapchainImageViews[i], m_imageDepth->getView() };
|
||||||
|
framebufferCI.attachmentCount = 2;
|
||||||
|
framebufferCI.pAttachments = views;
|
||||||
|
framebufferCI.width = m_swapchainExtent.width, framebufferCI.height = m_swapchainExtent.height;
|
||||||
|
framebufferCI.layers = 1;
|
||||||
|
|
||||||
|
if (vkCreateFramebuffer(m_device, &framebufferCI, nullptr, &m_swapchainFramebuffers[i]) != VK_SUCCESS)
|
||||||
|
NFError("Could not create framebuffer.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void RenderEngine::createBuffers() {
|
||||||
|
std::vector<Vertex> cubeVertices = {
|
||||||
|
{{-0.5, -0.5, 0.5}, {0.0, 1.0}},
|
||||||
|
{{ 0.5, -0.5, 0.5}, {1.0, 1.0}},
|
||||||
|
{{ 0.5, 0.5, 0.5}, {1.0, 0.0}},
|
||||||
|
{{-0.5, 0.5, 0.5}, {0.0, 0.0}},
|
||||||
|
|
||||||
|
{{ 0.5, 0.5, 0.5}, {0.0, 0.0}},
|
||||||
|
{{ 0.5, -0.5, -0.5}, {1.0, 1.0}},
|
||||||
|
{{ 0.5, 0.5, -0.5}, {1.0, 0.0}},
|
||||||
|
{{ 0.5, -0.5, 0.5}, {0.0, 1.0}},
|
||||||
|
|
||||||
|
{{-0.5, 0.5, -0.5}, {0.0, 0.0}},
|
||||||
|
{{-0.5, 0.5, 0.5}, {0.0, 1.0}},
|
||||||
|
{{ 0.5, 0.5, -0.5}, {1.0, 0.0}},
|
||||||
|
{{ 0.5, 0.5, 0.5}, {1.0, 1.0}},
|
||||||
|
|
||||||
|
{{-0.5, -0.5, -0.5}, {1.0, 0.0}},
|
||||||
|
{{-0.5, -0.5, 0.5}, {1.0, 1.0}},
|
||||||
|
{{ 0.5, -0.5, -0.5}, {0.0, 0.0}},
|
||||||
|
{{ 0.5, -0.5, 0.5}, {0.0, 1.0}},
|
||||||
|
|
||||||
|
{{-0.5, 0.5, -0.5}, {0.0, 0.0}},
|
||||||
|
{{-0.5, 0.5, 0.5}, {1.0, 0.0}},
|
||||||
|
{{-0.5, -0.5, 0.5}, {1.0, 1.0}},
|
||||||
|
{{-0.5, -0.5, -0.5}, {0.0, 1.0}},
|
||||||
|
|
||||||
|
{{ 0.5, 0.5, -0.5}, {1.0, 0.0}},
|
||||||
|
{{-0.5, 0.5, -0.5}, {0.0, 0.0}},
|
||||||
|
{{-0.5, -0.5, -0.5}, {0.0, 1.0}},
|
||||||
|
{{ 0.5, -0.5, -0.5}, {1.0, 1.0}},
|
||||||
|
|
||||||
|
{{ 0.5, -0.6, 0.5}, {1.0, 1.0}},
|
||||||
|
{{ 0.5, -0.6, -0.5}, {1.0, 0.0}},
|
||||||
|
{{-0.5, -0.6, 0.5}, {0.0, 1.0}}
|
||||||
|
};
|
||||||
|
|
||||||
|
size_t cubeVerticesSize = sizeof(cubeVertices[0]) * cubeVertices.size();
|
||||||
|
m_bufferVertex = std::make_unique<Buffer>(BufferType::Vertex, m_device, *m_allocator, *m_commandPool, cubeVertices.data(), cubeVerticesSize);
|
||||||
|
|
||||||
|
std::vector<uint32_t> cubeIndices = {
|
||||||
|
0, 1, 2,
|
||||||
|
2, 3, 0,
|
||||||
|
|
||||||
|
4, 5, 6,
|
||||||
|
4, 7, 5,
|
||||||
|
|
||||||
|
8, 9, 10,
|
||||||
|
10, 9, 11,
|
||||||
|
|
||||||
|
12, 14, 13,
|
||||||
|
14, 15, 13,
|
||||||
|
|
||||||
|
16, 19, 17,
|
||||||
|
17, 19, 18,
|
||||||
|
|
||||||
|
20, 23, 21,
|
||||||
|
23, 22, 21,
|
||||||
|
|
||||||
|
24, 25, 26
|
||||||
|
};
|
||||||
|
|
||||||
|
size_t cubeIndicesSize = sizeof(cubeIndices[0]) * cubeIndices.size();
|
||||||
|
m_bufferIndex = std::make_unique<Buffer>(BufferType::Index, m_device, *m_allocator, *m_commandPool, cubeIndices.data(), cubeIndicesSize);
|
||||||
|
|
||||||
|
size_t mvpUniformBufferSize = sizeof(glm::mat4);
|
||||||
|
m_bufferUniformMVP = std::make_unique<Buffer>(BufferType::Uniform, m_device, *m_allocator, *m_commandPool, nullptr, mvpUniformBufferSize);
|
||||||
|
}
|
||||||
|
|
||||||
|
void RenderEngine::createImage() {
|
||||||
|
std::string imageData;
|
||||||
|
util::readFile("grayson.jpg", imageData);
|
||||||
|
m_imageTest = std::make_unique<Image>(ImageType::Texture, m_device, *m_allocator, *m_commandPool, imageData);
|
||||||
|
|
||||||
|
VkSamplerCreateInfo samplerCI = { VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO };
|
||||||
|
samplerCI.magFilter = samplerCI.minFilter = VK_FILTER_LINEAR;
|
||||||
|
/*samplerCI.anisotropyEnable = VK_TRUE;
|
||||||
|
samplerCI.maxAnisotropy = 16;*/
|
||||||
|
|
||||||
|
if (vkCreateSampler(m_device, &samplerCI, nullptr, &m_sampler) != VK_SUCCESS)
|
||||||
|
NFError("Could not create sampler.");
|
||||||
|
}
|
||||||
|
|
||||||
|
void RenderEngine::createDescriptorSet() {
|
||||||
|
VkDescriptorSetAllocateInfo descriptorSetAI = { VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO };
|
||||||
|
descriptorSetAI.descriptorPool = m_pipelineOutputDescriptorPool;
|
||||||
|
descriptorSetAI.descriptorSetCount = 1;
|
||||||
|
descriptorSetAI.pSetLayouts = &m_pipelineOutputDescriptorSetLayout;
|
||||||
|
|
||||||
|
if (vkAllocateDescriptorSets(m_device, &descriptorSetAI, &m_pipelineOutputDescriptorSet) != VK_SUCCESS)
|
||||||
|
NFError("Could not allocate descriptor set.");
|
||||||
|
|
||||||
|
VkWriteDescriptorSet descriptorSetWrites[] = { {VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET}, {VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET} };
|
||||||
|
VkDescriptorBufferInfo uniformDescriptorBI = {};
|
||||||
|
uniformDescriptorBI.buffer = m_bufferUniformMVP->getHandle();
|
||||||
|
uniformDescriptorBI.range = VK_WHOLE_SIZE;
|
||||||
|
VkDescriptorImageInfo imageDescriptorII = {};
|
||||||
|
imageDescriptorII.imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
|
||||||
|
imageDescriptorII.imageView = m_imageTest->getView();
|
||||||
|
imageDescriptorII.sampler = m_sampler;
|
||||||
|
descriptorSetWrites[0].dstSet = m_pipelineOutputDescriptorSet;
|
||||||
|
descriptorSetWrites[0].dstBinding = 0;
|
||||||
|
descriptorSetWrites[0].descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER;
|
||||||
|
descriptorSetWrites[0].descriptorCount = 1;
|
||||||
|
descriptorSetWrites[0].pBufferInfo = &uniformDescriptorBI;
|
||||||
|
descriptorSetWrites[1].dstSet = m_pipelineOutputDescriptorSet;
|
||||||
|
descriptorSetWrites[1].dstBinding = 1;
|
||||||
|
descriptorSetWrites[1].descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
|
||||||
|
descriptorSetWrites[1].descriptorCount = 1;
|
||||||
|
descriptorSetWrites[1].pImageInfo = &imageDescriptorII;
|
||||||
|
vkUpdateDescriptorSets(m_device, 2, descriptorSetWrites, 0, nullptr);
|
||||||
|
}
|
||||||
|
|
||||||
|
void RenderEngine::doFrame() {
|
||||||
|
// First, wait for previous frame to complete
|
||||||
|
vkWaitForFences(m_device, 1, &m_fenceFrameInFlight, VK_TRUE, UINT64_MAX);
|
||||||
|
|
||||||
|
// Get next swapchain image and recreate if necessary
|
||||||
|
uint32_t nextImageIndex = 0;
|
||||||
|
VkResult result = vkAcquireNextImageKHR(m_device, m_swapchain, UINT64_MAX, m_semaphoreImageAvailable, nullptr, &nextImageIndex);
|
||||||
|
if (result == VK_ERROR_OUT_OF_DATE_KHR) {
|
||||||
|
recreateSwapchain();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
else if (result != VK_SUCCESS && result != VK_SUBOPTIMAL_KHR)
|
||||||
|
NFError("Could not aquire next swapchain image.");
|
||||||
|
|
||||||
|
// Reset fence for this frame
|
||||||
|
vkResetFences(m_device, 1, &m_fenceFrameInFlight);
|
||||||
|
|
||||||
|
// Begin recording
|
||||||
|
VkCommandBufferBeginInfo commandBufferBI = { VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO };
|
||||||
|
|
||||||
|
vkBeginCommandBuffer(m_commandBuffer, &commandBufferBI);
|
||||||
|
|
||||||
|
// Start the render pass
|
||||||
|
VkClearValue clearValues[] = { {{0.0, 0.0, 0.0, 1.0}}, {1.0, 0} };
|
||||||
|
VkRenderPassBeginInfo renderPassBI = { VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO };
|
||||||
|
renderPassBI.renderPass = m_renderPassOutput;
|
||||||
|
renderPassBI.framebuffer = m_swapchainFramebuffers[nextImageIndex];
|
||||||
|
renderPassBI.renderArea.extent = m_swapchainExtent;
|
||||||
|
renderPassBI.clearValueCount = 2;
|
||||||
|
renderPassBI.pClearValues = clearValues;
|
||||||
|
vkCmdBeginRenderPass(m_commandBuffer, &renderPassBI, VK_SUBPASS_CONTENTS_INLINE);
|
||||||
|
|
||||||
|
// Bind output pipeline
|
||||||
|
vkCmdBindPipeline(m_commandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, m_pipelineOutput);
|
||||||
|
|
||||||
|
// Set viewport and scissor
|
||||||
|
VkViewport viewport = {};
|
||||||
|
viewport.width = static_cast<float>(m_swapchainExtent.width), viewport.height = static_cast<float>(m_swapchainExtent.height);
|
||||||
|
viewport.maxDepth = 1.0;
|
||||||
|
vkCmdSetViewport(m_commandBuffer, 0, 1, &viewport);
|
||||||
|
VkRect2D scissor = { {}, m_swapchainExtent };
|
||||||
|
vkCmdSetScissor(m_commandBuffer, 0, 1, &scissor);
|
||||||
|
|
||||||
|
// Bind buffers
|
||||||
|
VkDeviceSize bufferOffset = 0;
|
||||||
|
vkCmdBindVertexBuffers(m_commandBuffer, 0, 1, &m_bufferVertex->getHandle(), &bufferOffset);
|
||||||
|
vkCmdBindIndexBuffer(m_commandBuffer, m_bufferIndex->getHandle(), 0, VK_INDEX_TYPE_UINT32);
|
||||||
|
|
||||||
|
// Update uniform buffer
|
||||||
|
static auto startTime = std::chrono::high_resolution_clock::now();
|
||||||
|
auto currentTime = std::chrono::high_resolution_clock::now();
|
||||||
|
float time = std::chrono::duration<float>(currentTime - startTime).count();
|
||||||
|
glm::mat4 modelMatrix = glm::rotate(glm::mat4(1.0), time * glm::radians(20.0f), glm::vec3(0.0, 1.0, 0.0)),
|
||||||
|
viewMatrix = glm::lookAt(glm::vec3(1.0, 1.0, 2.0), glm::vec3(0.0), glm::vec3(0.0, 1.0, 0.0)),
|
||||||
|
projectionMatrix = glm::perspective(glm::radians(45.0f), static_cast<float>(m_swapchainExtent.width) / m_swapchainExtent.height, 0.1f, 10.0f);
|
||||||
|
|
||||||
|
projectionMatrix[1][1] *= -1;
|
||||||
|
glm::mat4 mvpMatrix = projectionMatrix * viewMatrix * modelMatrix;
|
||||||
|
memcpy(m_bufferUniformMVP->getPointer(), &mvpMatrix, sizeof(mvpMatrix));
|
||||||
|
|
||||||
|
// Bind descriptors
|
||||||
|
vkCmdBindDescriptorSets(m_commandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, m_pipelineOutputLayout, 0, 1, &m_pipelineOutputDescriptorSet, 0, nullptr);
|
||||||
|
|
||||||
|
// Draw
|
||||||
|
vkCmdDrawIndexed(m_commandBuffer, m_bufferIndex->getIndicesCount(), 1, 0, 0, 0);
|
||||||
|
|
||||||
|
// End the render pass
|
||||||
|
vkCmdEndRenderPass(m_commandBuffer);
|
||||||
|
|
||||||
|
// Finish recording
|
||||||
|
vkEndCommandBuffer(m_commandBuffer);
|
||||||
|
|
||||||
|
// Submit to graphics queue
|
||||||
|
VkPipelineStageFlags waitStage = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
|
||||||
|
VkSubmitInfo submitInfo = { VK_STRUCTURE_TYPE_SUBMIT_INFO };
|
||||||
|
submitInfo.waitSemaphoreCount = 1;
|
||||||
|
submitInfo.pWaitSemaphores = &m_semaphoreImageAvailable;
|
||||||
|
submitInfo.pWaitDstStageMask = &waitStage;
|
||||||
|
submitInfo.commandBufferCount = 1;
|
||||||
|
submitInfo.pCommandBuffers = &m_commandBuffer;
|
||||||
|
submitInfo.signalSemaphoreCount = 1;
|
||||||
|
submitInfo.pSignalSemaphores = &m_semaphoreRenderingDone;
|
||||||
|
|
||||||
|
if (vkQueueSubmit(m_queueGraphics, 1, &submitInfo, m_fenceFrameInFlight) != VK_SUCCESS)
|
||||||
|
NFError("Could not submit to Vulkan queue.");
|
||||||
|
|
||||||
|
// And present!
|
||||||
|
VkPresentInfoKHR presentInfo = { VK_STRUCTURE_TYPE_PRESENT_INFO_KHR };
|
||||||
|
presentInfo.waitSemaphoreCount = 1;
|
||||||
|
presentInfo.pWaitSemaphores = &m_semaphoreRenderingDone;
|
||||||
|
presentInfo.swapchainCount = 1;
|
||||||
|
presentInfo.pSwapchains = &m_swapchain;
|
||||||
|
presentInfo.pImageIndices = &nextImageIndex;
|
||||||
|
|
||||||
|
result = vkQueuePresentKHR(m_queuePresent, &presentInfo);
|
||||||
|
if (result == VK_ERROR_OUT_OF_DATE_KHR || result == VK_SUBOPTIMAL_KHR)
|
||||||
|
recreateSwapchain();
|
||||||
|
else if (result != VK_SUCCESS)
|
||||||
|
NFError("Could not present image.");
|
||||||
|
}
|
||||||
|
|
||||||
|
void RenderEngine::setDisplay(DisplayConfig config) {
|
||||||
|
m_display = config;
|
||||||
|
m_window->setDisplay(m_display);
|
||||||
|
}
|
||||||
|
|
||||||
|
void RenderEngine::recreateSwapchain() {
|
||||||
|
// Might be here due to window closing
|
||||||
|
if (!m_window->isActive())
|
||||||
|
return;
|
||||||
|
|
||||||
|
waitIdle();
|
||||||
|
|
||||||
|
destroySwapchain();
|
||||||
|
|
||||||
|
createSwapchain();
|
||||||
|
createSwapchainFramebuffers();
|
||||||
|
}
|
||||||
|
|
||||||
|
void RenderEngine::destroySwapchain() {
|
||||||
|
for (int i = 0; i < m_swapchainFramebuffers.size(); i++)
|
||||||
|
vkDestroyFramebuffer(m_device, m_swapchainFramebuffers[i], nullptr);
|
||||||
|
|
||||||
|
m_imageDepth.reset();
|
||||||
|
|
||||||
|
for (int i = 0; i < m_swapchainImageViews.size(); i++)
|
||||||
|
vkDestroyImageView(m_device, m_swapchainImageViews[i], nullptr);
|
||||||
|
|
||||||
|
vkDestroySwapchainKHR(m_device, m_swapchain, nullptr);
|
||||||
|
}
|
||||||
|
|
||||||
|
void RenderEngine::waitIdle() {
|
||||||
|
vkDeviceWaitIdle(m_device);
|
||||||
|
}
|
||||||
|
|
||||||
|
RenderEngine::~RenderEngine() {
|
||||||
|
waitIdle();
|
||||||
|
|
||||||
|
vkDestroySampler(m_device, m_sampler, nullptr);
|
||||||
|
|
||||||
|
m_imageTest.reset();
|
||||||
|
|
||||||
|
m_bufferUniformMVP.reset();
|
||||||
|
m_bufferIndex.reset();
|
||||||
|
m_bufferVertex.reset();
|
||||||
|
|
||||||
|
vkDestroyPipeline(m_device, m_pipelineOutput, nullptr);
|
||||||
|
vkDestroyDescriptorPool(m_device, m_pipelineOutputDescriptorPool, nullptr);
|
||||||
|
vkDestroyPipelineLayout(m_device, m_pipelineOutputLayout, nullptr);
|
||||||
|
vkDestroyDescriptorSetLayout(m_device, m_pipelineOutputDescriptorSetLayout, nullptr);
|
||||||
|
vkDestroyRenderPass(m_device, m_renderPassOutput, nullptr);
|
||||||
|
destroySwapchain();
|
||||||
|
|
||||||
|
vkDestroyFence(m_device, m_fenceFrameInFlight, nullptr);
|
||||||
|
vkDestroySemaphore(m_device, m_semaphoreRenderingDone, nullptr);
|
||||||
|
vkDestroySemaphore(m_device, m_semaphoreImageAvailable, nullptr);
|
||||||
|
|
||||||
|
m_commandPool.reset();
|
||||||
|
|
||||||
|
vkDestroyDevice(m_device, nullptr);
|
||||||
|
vkDestroySurfaceKHR(m_instance, m_surface, nullptr);
|
||||||
|
vkDestroyInstance(m_instance, nullptr);
|
||||||
|
}
|
||||||
|
}
|
85
NothinFancy/src/client/render/RenderEngine.h
Normal file
85
NothinFancy/src/client/render/RenderEngine.h
Normal file
@ -0,0 +1,85 @@
|
|||||||
|
// RenderEngine class header
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "client/Window.h"
|
||||||
|
#include "nf/config.h"
|
||||||
|
#include "CommandPool.h"
|
||||||
|
#include "VideoMemoryAllocator.h"
|
||||||
|
#include "Buffer.h"
|
||||||
|
#include "Image.h"
|
||||||
|
|
||||||
|
namespace nf::client::render {
|
||||||
|
class RenderEngine {
|
||||||
|
public:
|
||||||
|
RenderEngine(std::shared_ptr<Window> window, DisplayConfig display);
|
||||||
|
|
||||||
|
void doFrame();
|
||||||
|
void setDisplay(DisplayConfig config);
|
||||||
|
|
||||||
|
~RenderEngine();
|
||||||
|
private:
|
||||||
|
void createInstance();
|
||||||
|
void createSurface();
|
||||||
|
void pickPhysicalDevice();
|
||||||
|
void createDevice();
|
||||||
|
void createExecutionObjects();
|
||||||
|
|
||||||
|
void createSwapchain();
|
||||||
|
|
||||||
|
void createOutputRenderPass();
|
||||||
|
void createOutputPipeline();
|
||||||
|
|
||||||
|
void createSwapchainFramebuffers();
|
||||||
|
void createBuffers();
|
||||||
|
void createImage();
|
||||||
|
void createDescriptorSet();
|
||||||
|
|
||||||
|
void recreateSwapchain();
|
||||||
|
void destroySwapchain();
|
||||||
|
|
||||||
|
void waitIdle();
|
||||||
|
|
||||||
|
std::shared_ptr<Window> m_window;
|
||||||
|
DisplayConfig m_display;
|
||||||
|
|
||||||
|
// Global state
|
||||||
|
VkInstance m_instance;
|
||||||
|
VkSurfaceKHR m_surface;
|
||||||
|
VkPhysicalDevice m_physicalDevice;
|
||||||
|
uint32_t m_queueFIGraphics, m_queueFIPresent;
|
||||||
|
VkDevice m_device;
|
||||||
|
VkQueue m_queueGraphics, m_queuePresent;
|
||||||
|
|
||||||
|
// Execution objects
|
||||||
|
std::unique_ptr<CommandPool> m_commandPool;
|
||||||
|
VkCommandBuffer m_commandBuffer;
|
||||||
|
VkSemaphore m_semaphoreImageAvailable;
|
||||||
|
VkSemaphore m_semaphoreRenderingDone;
|
||||||
|
VkFence m_fenceFrameInFlight;
|
||||||
|
|
||||||
|
// Swapchain
|
||||||
|
VkSwapchainKHR m_swapchain;
|
||||||
|
VkFormat m_swapchainImageFormat;
|
||||||
|
VkExtent2D m_swapchainExtent;
|
||||||
|
std::vector<VkImage> m_swapchainImages;
|
||||||
|
std::vector<VkImageView> m_swapchainImageViews;
|
||||||
|
|
||||||
|
// RenderPass and Pipeline
|
||||||
|
VkRenderPass m_renderPassOutput;
|
||||||
|
VkDescriptorSetLayout m_pipelineOutputDescriptorSetLayout;
|
||||||
|
VkPipelineLayout m_pipelineOutputLayout;
|
||||||
|
VkDescriptorPool m_pipelineOutputDescriptorPool;
|
||||||
|
VkPipeline m_pipelineOutput;
|
||||||
|
|
||||||
|
// Temporary objects
|
||||||
|
std::unique_ptr<VideoMemoryAllocator> m_allocator;
|
||||||
|
std::unique_ptr<Image> m_imageDepth;
|
||||||
|
std::vector<VkFramebuffer> m_swapchainFramebuffers;
|
||||||
|
std::unique_ptr<Buffer> m_bufferVertex;
|
||||||
|
std::unique_ptr<Buffer> m_bufferIndex;
|
||||||
|
std::unique_ptr<Buffer> m_bufferUniformMVP;
|
||||||
|
std::unique_ptr<Image> m_imageTest;
|
||||||
|
VkSampler m_sampler;
|
||||||
|
VkDescriptorSet m_pipelineOutputDescriptorSet;
|
||||||
|
};
|
||||||
|
}
|
27
NothinFancy/src/client/render/ShaderModule.cpp
Normal file
27
NothinFancy/src/client/render/ShaderModule.cpp
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
// ShaderModule class implementation
|
||||||
|
#include "pch.h"
|
||||||
|
|
||||||
|
#include "ShaderModule.h"
|
||||||
|
#include "util.h"
|
||||||
|
|
||||||
|
namespace nf::client::render {
|
||||||
|
ShaderModule::ShaderModule(const VkDevice& device, const std::string& shaderBinary)
|
||||||
|
: GraphicsResource(device)
|
||||||
|
, m_shaderModule()
|
||||||
|
{
|
||||||
|
VkShaderModuleCreateInfo shaderModuleCI = { VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO };
|
||||||
|
shaderModuleCI.codeSize = shaderBinary.size();
|
||||||
|
shaderModuleCI.pCode = reinterpret_cast<const uint32_t*>(shaderBinary.data());
|
||||||
|
|
||||||
|
if (vkCreateShaderModule(device, &shaderModuleCI, nullptr, &m_shaderModule) != VK_SUCCESS)
|
||||||
|
NFError("Could not create shader module.");
|
||||||
|
}
|
||||||
|
|
||||||
|
VkShaderModule& ShaderModule::getHandle() {
|
||||||
|
return m_shaderModule;
|
||||||
|
}
|
||||||
|
|
||||||
|
ShaderModule::~ShaderModule() {
|
||||||
|
vkDestroyShaderModule(m_device, m_shaderModule, nullptr);
|
||||||
|
}
|
||||||
|
}
|
17
NothinFancy/src/client/render/ShaderModule.h
Normal file
17
NothinFancy/src/client/render/ShaderModule.h
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
// ShaderModule class header
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "GraphicsResource.h"
|
||||||
|
|
||||||
|
namespace nf::client::render {
|
||||||
|
class ShaderModule : GraphicsResource {
|
||||||
|
public:
|
||||||
|
ShaderModule(const VkDevice& device, const std::string& shaderBinary);
|
||||||
|
|
||||||
|
VkShaderModule& getHandle();
|
||||||
|
|
||||||
|
~ShaderModule();
|
||||||
|
private:
|
||||||
|
VkShaderModule m_shaderModule;
|
||||||
|
};
|
||||||
|
}
|
145
NothinFancy/src/client/render/VideoMemoryAllocator.cpp
Normal file
145
NothinFancy/src/client/render/VideoMemoryAllocator.cpp
Normal file
@ -0,0 +1,145 @@
|
|||||||
|
// VideoMemoryAllocator class implementation
|
||||||
|
#include "pch.h"
|
||||||
|
|
||||||
|
#include "VideoMemoryAllocator.h"
|
||||||
|
#include "util.h"
|
||||||
|
|
||||||
|
// 1 GiB
|
||||||
|
#define HEAP_SIZE_LARGE 1024ull * 1024 * 1024
|
||||||
|
// 50 MiB
|
||||||
|
#define DEFAULT_ALLOCATION_AMOUNT 50ull * 1024 * 1024
|
||||||
|
|
||||||
|
namespace nf::client::render {
|
||||||
|
VideoMemoryAllocator::VideoMemoryAllocator(const VkDevice& device, const VkPhysicalDevice& physicalDevice)
|
||||||
|
: m_device(device)
|
||||||
|
, m_physicalDevice(physicalDevice)
|
||||||
|
, m_blocks()
|
||||||
|
{}
|
||||||
|
|
||||||
|
void VideoMemoryAllocator::allocateForBuffer(VkBuffer buffer, VkMemoryPropertyFlags memoryPropertyFlags, VideoMemoryAllocation& outAllocation) {
|
||||||
|
VkMemoryRequirements memoryRequirements = {};
|
||||||
|
vkGetBufferMemoryRequirements(m_device, buffer, &memoryRequirements);
|
||||||
|
|
||||||
|
allocate(memoryRequirements, memoryPropertyFlags, outAllocation);
|
||||||
|
vkBindBufferMemory(m_device, buffer, outAllocation.memory, outAllocation.offset);
|
||||||
|
}
|
||||||
|
|
||||||
|
void VideoMemoryAllocator::allocateForImage(VkImage image, VkMemoryPropertyFlags memoryPropertyFlags, VideoMemoryAllocation& outAllocation) {
|
||||||
|
VkMemoryRequirements memoryRequirements = {};
|
||||||
|
vkGetImageMemoryRequirements(m_device, image, &memoryRequirements);
|
||||||
|
|
||||||
|
allocate(memoryRequirements, memoryPropertyFlags, outAllocation);
|
||||||
|
vkBindImageMemory(m_device, image, outAllocation.memory, outAllocation.offset);
|
||||||
|
}
|
||||||
|
|
||||||
|
void VideoMemoryAllocator::deallocate(const VideoMemoryAllocation& allocation) {
|
||||||
|
const auto blockIterator = std::find_if(m_blocks.begin(), m_blocks.end(), [&](const auto& currentBlock) {
|
||||||
|
if (currentBlock.memory == allocation.memory)
|
||||||
|
return true;
|
||||||
|
else return false;
|
||||||
|
});
|
||||||
|
|
||||||
|
if (blockIterator == m_blocks.end())
|
||||||
|
NFError("Could not find video memory block to deallocate from.");
|
||||||
|
|
||||||
|
MemoryBlock& block = *blockIterator;
|
||||||
|
|
||||||
|
const auto allocationIterator = block.allocations.find(allocation.offset);
|
||||||
|
|
||||||
|
if (allocationIterator == block.allocations.end())
|
||||||
|
NFError("Could not find video memory allocation to deallocate.");
|
||||||
|
|
||||||
|
block.allocations.erase(allocationIterator);
|
||||||
|
|
||||||
|
// If block is now empty, free it
|
||||||
|
if (block.allocations.empty()) {
|
||||||
|
vkFreeMemory(m_device, block.memory, nullptr);
|
||||||
|
m_blocks.erase(blockIterator);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void VideoMemoryAllocator::allocate(VkMemoryRequirements memoryRequirements, VkMemoryPropertyFlags memoryPropertyFlags, VideoMemoryAllocation& outAllocation) {
|
||||||
|
uint32_t memoryTypeIndex = findMemoryType(memoryRequirements.memoryTypeBits, memoryPropertyFlags);
|
||||||
|
|
||||||
|
if (memoryTypeIndex == -1)
|
||||||
|
NFError("Could not find suitable memory type.");
|
||||||
|
|
||||||
|
while (true) {
|
||||||
|
for (auto& block : m_blocks) {
|
||||||
|
if (block.memoryTypeIndex == memoryTypeIndex) {
|
||||||
|
VkDeviceSize lastOffset = 0, lastSize = 0;
|
||||||
|
// Check between existing allocations
|
||||||
|
for (const auto& [currentOffset, currentSize] : block.allocations) {
|
||||||
|
// Check for alignment
|
||||||
|
VkDeviceSize checkOffset = lastOffset + lastSize, remainder = checkOffset % memoryRequirements.alignment;
|
||||||
|
checkOffset = remainder ? checkOffset + (memoryRequirements.alignment - remainder) : checkOffset;
|
||||||
|
// Check for enough space
|
||||||
|
if (currentOffset - lastOffset - lastSize >= memoryRequirements.size) {
|
||||||
|
outAllocation.memory = block.memory;
|
||||||
|
outAllocation.offset = checkOffset;
|
||||||
|
if (block.mappedMemoryPointer)
|
||||||
|
outAllocation.mappedMemoryPointer = reinterpret_cast<char*>(block.mappedMemoryPointer) + outAllocation.offset;
|
||||||
|
block.allocations[checkOffset] = memoryRequirements.size;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
lastOffset = currentOffset, lastSize = currentSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check end
|
||||||
|
VkDeviceSize checkOffset = lastOffset + lastSize, remainder = checkOffset % memoryRequirements.alignment;
|
||||||
|
checkOffset = remainder ? checkOffset + (memoryRequirements.alignment - remainder) : checkOffset;
|
||||||
|
if ((block.size - 1) - lastOffset - lastSize >= memoryRequirements.size) {
|
||||||
|
outAllocation.memory = block.memory;
|
||||||
|
outAllocation.offset = checkOffset;
|
||||||
|
if (block.mappedMemoryPointer)
|
||||||
|
outAllocation.mappedMemoryPointer = reinterpret_cast<char*>(block.mappedMemoryPointer) + outAllocation.offset;
|
||||||
|
block.allocations[checkOffset] = memoryRequirements.size;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If not enough space, continue
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// No suitable block found, allocate one
|
||||||
|
VkMemoryAllocateInfo newBlockAllocateInfo = { VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO };
|
||||||
|
newBlockAllocateInfo.memoryTypeIndex = memoryTypeIndex;
|
||||||
|
newBlockAllocateInfo.allocationSize = calculateBlockSize(memoryTypeIndex);
|
||||||
|
allocateBlock(newBlockAllocateInfo);
|
||||||
|
|
||||||
|
// If host visible, map
|
||||||
|
if (memoryPropertyFlags & VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT)
|
||||||
|
vkMapMemory(m_device, m_blocks.back().memory, 0, VK_WHOLE_SIZE, 0, &m_blocks.back().mappedMemoryPointer);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t VideoMemoryAllocator::findMemoryType(uint32_t typeFilter, VkMemoryPropertyFlags desiredPropertyFlags) {
|
||||||
|
VkPhysicalDeviceMemoryProperties memoryProperties = {};
|
||||||
|
vkGetPhysicalDeviceMemoryProperties(m_physicalDevice, &memoryProperties);
|
||||||
|
|
||||||
|
for (int i = 0; i < memoryProperties.memoryTypeCount; i++)
|
||||||
|
if (typeFilter & (1 << i) && (memoryProperties.memoryTypes[i].propertyFlags & desiredPropertyFlags) == desiredPropertyFlags)
|
||||||
|
return i;
|
||||||
|
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t VideoMemoryAllocator::calculateBlockSize(uint32_t memoryTypeIndex) const {
|
||||||
|
VkPhysicalDeviceMemoryProperties memoryProperties = {};
|
||||||
|
vkGetPhysicalDeviceMemoryProperties(m_physicalDevice, &memoryProperties);
|
||||||
|
size_t heapSize = memoryProperties.memoryHeaps[memoryProperties.memoryTypes[memoryTypeIndex].heapIndex].size;
|
||||||
|
|
||||||
|
return heapSize > HEAP_SIZE_LARGE ? DEFAULT_ALLOCATION_AMOUNT : (heapSize / 8);
|
||||||
|
}
|
||||||
|
|
||||||
|
void VideoMemoryAllocator::allocateBlock(VkMemoryAllocateInfo& allocateInfo) {
|
||||||
|
VkDeviceMemory memoryBlock = nullptr;
|
||||||
|
|
||||||
|
if (vkAllocateMemory(m_device, &allocateInfo, nullptr, &memoryBlock) != VK_SUCCESS)
|
||||||
|
NFError("Could not allocate video memory.");
|
||||||
|
|
||||||
|
m_blocks.emplace_back(memoryBlock, allocateInfo.memoryTypeIndex, allocateInfo.allocationSize, nullptr);
|
||||||
|
}
|
||||||
|
}
|
41
NothinFancy/src/client/render/VideoMemoryAllocator.h
Normal file
41
NothinFancy/src/client/render/VideoMemoryAllocator.h
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
// VideoMemoryAllocator class header
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
namespace nf::client::render {
|
||||||
|
struct VideoMemoryAllocation {
|
||||||
|
VkDeviceMemory memory;
|
||||||
|
VkDeviceSize offset;
|
||||||
|
void* mappedMemoryPointer;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct MemoryBlock {
|
||||||
|
VkDeviceMemory memory;
|
||||||
|
uint32_t memoryTypeIndex;
|
||||||
|
size_t size;
|
||||||
|
void* mappedMemoryPointer;
|
||||||
|
|
||||||
|
std::map<VkDeviceSize, VkDeviceSize> allocations;
|
||||||
|
// Offset Size
|
||||||
|
};
|
||||||
|
|
||||||
|
class VideoMemoryAllocator {
|
||||||
|
public:
|
||||||
|
VideoMemoryAllocator(const VkDevice& device, const VkPhysicalDevice& physicalDevice);
|
||||||
|
|
||||||
|
void allocateForBuffer(VkBuffer buffer, VkMemoryPropertyFlags memoryPropertyFlags, VideoMemoryAllocation& outAllocation);
|
||||||
|
void allocateForImage(VkImage image, VkMemoryPropertyFlags memoryPropertyFlags, VideoMemoryAllocation& outAllocation);
|
||||||
|
|
||||||
|
void deallocate(const VideoMemoryAllocation& allocation);
|
||||||
|
private:
|
||||||
|
void allocate(VkMemoryRequirements memoryRequirements, VkMemoryPropertyFlags memoryPropertyFlags, VideoMemoryAllocation& outAllocation);
|
||||||
|
uint32_t findMemoryType(uint32_t typeFilter, VkMemoryPropertyFlags desiredPropertyFlags);
|
||||||
|
|
||||||
|
size_t calculateBlockSize(uint32_t memoryTypeIndex) const;
|
||||||
|
void allocateBlock(VkMemoryAllocateInfo& allocateInfo);
|
||||||
|
|
||||||
|
const VkDevice& m_device;
|
||||||
|
const VkPhysicalDevice& m_physicalDevice;
|
||||||
|
|
||||||
|
std::vector<MemoryBlock> m_blocks;
|
||||||
|
};
|
||||||
|
}
|
28
NothinFancy/src/include/nf.h
Normal file
28
NothinFancy/src/include/nf.h
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
// NF main public header
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "nf/config.h"
|
||||||
|
|
||||||
|
namespace nf {
|
||||||
|
struct CommandLineArguments {
|
||||||
|
int argc;
|
||||||
|
char** argv;
|
||||||
|
};
|
||||||
|
|
||||||
|
ClientConfig configureEngine(CommandLineArguments cmdArgs);
|
||||||
|
|
||||||
|
void runEngine(ClientConfig config);
|
||||||
|
}
|
||||||
|
|
||||||
|
// NF entry point
|
||||||
|
#ifdef NFENTRY
|
||||||
|
|
||||||
|
int main(int argc, char* argv[]) {
|
||||||
|
nf::ClientConfig config = nf::configureEngine({ argc, argv });
|
||||||
|
|
||||||
|
nf::runEngine(config);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
32
NothinFancy/src/include/nf/config.h
Normal file
32
NothinFancy/src/include/nf/config.h
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
// EngineConfig struct public header
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
namespace nf {
|
||||||
|
enum class DisplayMode {
|
||||||
|
Windowed,
|
||||||
|
Fullscreen
|
||||||
|
};
|
||||||
|
|
||||||
|
struct DisplayConfig {
|
||||||
|
DisplayMode mode;
|
||||||
|
unsigned int width, height;
|
||||||
|
|
||||||
|
DisplayConfig()
|
||||||
|
: mode(DisplayMode::Windowed)
|
||||||
|
, width(1280)
|
||||||
|
, height(720)
|
||||||
|
{}
|
||||||
|
};
|
||||||
|
|
||||||
|
struct ClientConfig {
|
||||||
|
const char* appName;
|
||||||
|
const char* appVersion;
|
||||||
|
DisplayConfig display;
|
||||||
|
|
||||||
|
ClientConfig()
|
||||||
|
: appName("Nothin' Fancy Game")
|
||||||
|
, appVersion("v0.1.0")
|
||||||
|
, display()
|
||||||
|
{}
|
||||||
|
};
|
||||||
|
}
|
49
NothinFancy/src/pch.h
Normal file
49
NothinFancy/src/pch.h
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
// NF precompiled header
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
// IO and strings
|
||||||
|
#include <iostream>
|
||||||
|
#include <fstream>
|
||||||
|
#include <filesystem>
|
||||||
|
#include <sstream>
|
||||||
|
#include <string>
|
||||||
|
#include <format>
|
||||||
|
|
||||||
|
// Containers
|
||||||
|
#include <vector>
|
||||||
|
#include <array>
|
||||||
|
#include <queue>
|
||||||
|
#include <stack>
|
||||||
|
#include <set>
|
||||||
|
#include <unordered_set>
|
||||||
|
#include <map>
|
||||||
|
#include <unordered_map>
|
||||||
|
|
||||||
|
// Multithreading
|
||||||
|
#include <thread>
|
||||||
|
#include <mutex>
|
||||||
|
#include <atomic>
|
||||||
|
#include <future>
|
||||||
|
#include <condition_variable>
|
||||||
|
|
||||||
|
// Miscellaneous
|
||||||
|
#include <chrono>
|
||||||
|
#include <utility>
|
||||||
|
#include <memory>
|
||||||
|
#include <algorithm>
|
||||||
|
#include <numbers>
|
||||||
|
#include <random>
|
||||||
|
|
||||||
|
// Windows
|
||||||
|
#define WIN32_LEAN_AND_MEAN
|
||||||
|
#include <Windows.h>
|
||||||
|
#undef max
|
||||||
|
|
||||||
|
// Vulkan
|
||||||
|
#define VK_USE_PLATFORM_WIN32_KHR
|
||||||
|
#include <vulkan/vulkan.h>
|
||||||
|
|
||||||
|
// GLM
|
||||||
|
#define GLM_FORCE_DEPTH_ZERO_TO_ONE
|
||||||
|
#include <glm/glm.hpp>
|
||||||
|
#include <glm/gtc/matrix_transform.hpp>
|
26
NothinFancy/src/util.h
Normal file
26
NothinFancy/src/util.h
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
// Miscellaneous utilities header
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "util/log.h"
|
||||||
|
#include "util/file.h"
|
||||||
|
|
||||||
|
// Define NFTime
|
||||||
|
#ifdef _DEBUG
|
||||||
|
#define NFTime() ::nf::util::ScopedTimer __scopeTimer(__FUNCSIG__)
|
||||||
|
#else
|
||||||
|
#define NFTime()
|
||||||
|
#endif
|
||||||
|
|
||||||
|
namespace nf::util {
|
||||||
|
double getRand();
|
||||||
|
double getRandRange(double minimum, double maximum);
|
||||||
|
|
||||||
|
class ScopedTimer {
|
||||||
|
public:
|
||||||
|
ScopedTimer(const char* funcName);
|
||||||
|
~ScopedTimer();
|
||||||
|
private:
|
||||||
|
const char* m_funcName;
|
||||||
|
std::chrono::time_point<std::chrono::high_resolution_clock> m_startTime;
|
||||||
|
};
|
||||||
|
}
|
25
NothinFancy/src/util/file.cpp
Normal file
25
NothinFancy/src/util/file.cpp
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
// File IO functions implementation
|
||||||
|
#include "pch.h"
|
||||||
|
|
||||||
|
namespace nf::util {
|
||||||
|
bool readFile(const char* filename, std::string& out) {
|
||||||
|
std::ifstream fileStream(filename, std::ios::binary | std::ios::ate);
|
||||||
|
if (!fileStream.is_open())
|
||||||
|
return false;
|
||||||
|
|
||||||
|
size_t fileSize = fileStream.tellg();
|
||||||
|
out.resize(fileSize);
|
||||||
|
fileStream.seekg(0);
|
||||||
|
fileStream.read(out.data(), fileSize);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool writeFile(const char* filename, const std::string& in) {
|
||||||
|
std::ofstream fileStream(filename, std::ios::binary | std::ios::trunc);
|
||||||
|
if (!fileStream.is_open())
|
||||||
|
return false;
|
||||||
|
|
||||||
|
fileStream << in;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
7
NothinFancy/src/util/file.h
Normal file
7
NothinFancy/src/util/file.h
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
// File IO functions header
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
namespace nf::util {
|
||||||
|
bool readFile(const char* filename, std::string& out);
|
||||||
|
bool writeFile(const char* filename, const std::string& in);
|
||||||
|
}
|
64
NothinFancy/src/util/log.cpp
Normal file
64
NothinFancy/src/util/log.cpp
Normal file
@ -0,0 +1,64 @@
|
|||||||
|
// NF logging system implementation
|
||||||
|
#include "pch.h"
|
||||||
|
|
||||||
|
#include "log.h"
|
||||||
|
|
||||||
|
namespace nf::util {
|
||||||
|
// Log is only present in debug builds
|
||||||
|
#ifdef _DEBUG
|
||||||
|
|
||||||
|
// Mutex to synchronize access to stdout
|
||||||
|
static std::mutex s_logMutex;
|
||||||
|
// Needed to enable Win32 virtual terminal processing
|
||||||
|
static bool s_isLogInit = false;
|
||||||
|
// When application was started
|
||||||
|
static std::chrono::time_point s_initTime = std::chrono::high_resolution_clock::now();
|
||||||
|
// Mapping of type strings
|
||||||
|
static std::map<LogType, const char*> s_logTypesMap = {
|
||||||
|
{LogType::Log, "\x1b[93mLog\x1b[0m"},
|
||||||
|
{LogType::Timing, "\x1b[92mTiming\x1b[0m"},
|
||||||
|
{LogType::Error, "\x1b[91mError\x1b[0m"}
|
||||||
|
};
|
||||||
|
|
||||||
|
static void initLog() {
|
||||||
|
HANDLE stdoutHandle = GetStdHandle(STD_OUTPUT_HANDLE);
|
||||||
|
DWORD consoleMode = 0;
|
||||||
|
GetConsoleMode(stdoutHandle, &consoleMode);
|
||||||
|
consoleMode |= ENABLE_VIRTUAL_TERMINAL_PROCESSING;
|
||||||
|
SetConsoleMode(stdoutHandle, consoleMode);
|
||||||
|
s_isLogInit = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void log(const char* msg, LogType type) {
|
||||||
|
std::lock_guard logLock(s_logMutex);
|
||||||
|
|
||||||
|
if (!s_isLogInit)
|
||||||
|
initLog();
|
||||||
|
|
||||||
|
auto time = std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::high_resolution_clock::now() - s_initTime);
|
||||||
|
|
||||||
|
std::cout << std::format("[{:9.3f}][NF {}]:\t{}\n", time.count() * 1e-3, s_logTypesMap[type], msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
void log(const std::string& msg, LogType type) {
|
||||||
|
log(msg.c_str(), type);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
||||||
|
void error(const char* msg, const char* file, unsigned int line) {
|
||||||
|
std::string filename = file;
|
||||||
|
filename = filename.substr(filename.rfind("\\src\\") + 5);
|
||||||
|
|
||||||
|
#ifdef _DEBUG
|
||||||
|
log(std::format("({}, {}) {}", filename, line, msg), LogType::Error);
|
||||||
|
MessageBeep(0);
|
||||||
|
__debugbreak();
|
||||||
|
#else
|
||||||
|
std::string fmtMsg = std::format("{}\n\n({} at line {})", msg, filename, line);
|
||||||
|
MessageBox(nullptr, fmtMsg.c_str(), "Engine Error", MB_OK | MB_ICONERROR);
|
||||||
|
std::exit(1);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
30
NothinFancy/src/util/log.h
Normal file
30
NothinFancy/src/util/log.h
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
// NF logging and error system header
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
// Define NFLog
|
||||||
|
#ifdef _DEBUG // Debug builds
|
||||||
|
#define NFLog(x) ::nf::util::log(x, ::nf::util::LogType::Log)
|
||||||
|
#else // Release builds
|
||||||
|
#define NFLog(x)
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// Define NFError
|
||||||
|
#define NFError(x) ::nf::util::error(x, __FILE__, __LINE__)
|
||||||
|
|
||||||
|
namespace nf::util {
|
||||||
|
enum class LogType {
|
||||||
|
Log,
|
||||||
|
Timing,
|
||||||
|
Error
|
||||||
|
};
|
||||||
|
|
||||||
|
void log(const char* msg, LogType type);
|
||||||
|
void log(const std::string& msg, LogType type);
|
||||||
|
|
||||||
|
template<typename T>
|
||||||
|
void log(T data, LogType type) {
|
||||||
|
log(std::to_string(data), type);
|
||||||
|
}
|
||||||
|
|
||||||
|
void error(const char* msg, const char* file, unsigned int line);
|
||||||
|
}
|
25
NothinFancy/src/util/util.cpp
Normal file
25
NothinFancy/src/util/util.cpp
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
// Miscellaneous utilities implementation
|
||||||
|
#include "pch.h"
|
||||||
|
|
||||||
|
#include "util.h"
|
||||||
|
|
||||||
|
namespace nf::util {
|
||||||
|
double getRand() {
|
||||||
|
static std::random_device dev;
|
||||||
|
return static_cast<double>(dev()) / dev.max();
|
||||||
|
}
|
||||||
|
|
||||||
|
double getRandRange(double minimum, double maximum) {
|
||||||
|
return getRand() * (maximum - minimum) + minimum;
|
||||||
|
}
|
||||||
|
|
||||||
|
ScopedTimer::ScopedTimer(const char* funcName)
|
||||||
|
: m_funcName(funcName)
|
||||||
|
, m_startTime(std::chrono::high_resolution_clock::now())
|
||||||
|
{}
|
||||||
|
|
||||||
|
ScopedTimer::~ScopedTimer() {
|
||||||
|
auto duration = std::chrono::duration_cast<std::chrono::microseconds>(std::chrono::high_resolution_clock::now() - m_startTime);
|
||||||
|
::nf::util::log(std::format("Scope in {} took {:.3f} ms", m_funcName, duration.count() * 1e-3), ::nf::util::LogType::Timing);
|
||||||
|
}
|
||||||
|
}
|
2
NothinFancy/src/version.h.in
Normal file
2
NothinFancy/src/version.h.in
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
// Version configured header
|
||||||
|
#define NFVERSION "@NFVERSION@"
|
23
TestGame/CMakeLists.txt
Normal file
23
TestGame/CMakeLists.txt
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
# TestGame app CMakeLists.txt
|
||||||
|
add_executable(TestGame WIN32 "src/TestGame.cpp")
|
||||||
|
|
||||||
|
# Use C++20
|
||||||
|
set_property(TARGET TestGame PROPERTY CXX_STANDARD 20)
|
||||||
|
|
||||||
|
# Use "main" function
|
||||||
|
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} /entry:mainCRTStartup")
|
||||||
|
|
||||||
|
# Keep the console in debug
|
||||||
|
if(${CMAKE_BUILD_TYPE} STREQUAL "Debug")
|
||||||
|
set_property(TARGET TestGame PROPERTY WIN32_EXECUTABLE FALSE)
|
||||||
|
endif()
|
||||||
|
|
||||||
|
# Link to NF library
|
||||||
|
target_link_libraries(TestGame NothinFancy)
|
||||||
|
target_include_directories(TestGame PUBLIC "${CMAKE_SOURCE_DIR}/NothinFancy/src/include")
|
||||||
|
|
||||||
|
# Copy shaders to executable directory
|
||||||
|
add_custom_command(TARGET TestGame POST_BUILD
|
||||||
|
COMMAND ${CMAKE_COMMAND} -E make_directory "${PROJECT_BINARY_DIR}/TestGame/shaders"
|
||||||
|
COMMAND ${CMAKE_COMMAND} -E copy_directory "${PROJECT_BINARY_DIR}/NothinFancy/shaders" "${PROJECT_BINARY_DIR}/TestGame/shaders"
|
||||||
|
)
|
12
TestGame/src/TestGame.cpp
Normal file
12
TestGame/src/TestGame.cpp
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
// TestGame main file
|
||||||
|
#define NFENTRY
|
||||||
|
#include "nf.h"
|
||||||
|
|
||||||
|
namespace nf {
|
||||||
|
ClientConfig configureEngine(CommandLineArguments cmdArgs) {
|
||||||
|
ClientConfig config;
|
||||||
|
config.appName = "TestGame";
|
||||||
|
//config.display.mode = DisplayMode::Fullscreen;
|
||||||
|
return config;
|
||||||
|
}
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user