Compare commits

..
33 Commits
Author SHA1 Message Date
grayson 3088a42a07 Two frames in flight 2025-02-13 13:37:15 -06:00
grayson f9232f314b Fix fullscreen rendering 2025-02-13 12:57:32 -06:00
grayson 160ca7070f Window setDisplay no longer hides window 2025-02-13 12:42:53 -06:00
grayson c5e1a66e36 Add doFrame function 2025-02-12 04:20:30 -06:00
grayson ebe78cb4ca Create synchronization objects 2025-02-12 03:24:16 -06:00
grayson d30b37bfbd Create command pool and allocate command buffer 2025-02-11 19:45:47 -06:00
grayson b1ac87d304 Create swapchain framebuffers 2025-02-11 19:33:58 -06:00
grayson 72d32fba6f Create output pipeline 2025-02-11 19:17:45 -06:00
grayson 92af78a6b0 Add ShaderModule 2025-02-11 18:01:43 -06:00
grayson bfacea2bd4 Create shader modules for output pipeline 2025-02-11 16:46:18 -06:00
grayson f5a06e6ac1 Create swapchain image views 2025-02-11 12:55:24 -06:00
grayson 1824b73d66 Create Vulkan swapchain 2025-02-11 03:24:53 -06:00
grayson b0005c250f Create output render pass 2025-02-09 16:39:45 -06:00
grayson 2b39c6e15b Create Vulkan device 2025-02-09 01:25:14 -06:00
grayson 89cdf712b8 Choose Vulkan physical device 2025-02-09 01:09:14 -06:00
grayson d53fb94e4c Enable visual styles 2025-02-08 03:11:12 -06:00
grayson b87c2164ad Create Window on separate input thread 2025-02-08 03:01:55 -06:00
grayson 0205f70ba6 Create Vulkan surface 2025-02-04 23:50:36 -06:00
grayson ece828d045 Add RenderEngine and create Vulkan instance 2025-02-04 13:30:06 -06:00
grayson 474b096337 Set window title 2025-02-02 15:10:13 -06:00
grayson 9c78fe09c9 Add fullscreen 2025-02-02 15:05:58 -06:00
grayson bd8eda13aa Prevent window resize on DPI change 2025-02-02 13:12:27 -06:00
grayson 91afd4ad1b Add Client and Window 2025-01-31 15:12:40 -06:00
grayson 1f1e62abdc Add file IO functions 2025-01-30 13:36:43 -06:00
grayson 986719a405 Add random functions and align log 2025-01-30 13:29:01 -06:00
grayson a31c7ab55b Add scoped timing 2025-01-30 13:06:50 -06:00
grayson ccfbc86f4f Add timestamp to log 2025-01-30 12:49:39 -06:00
grayson 0c0c46e23d Add entry point and debug texts 2025-01-30 12:39:59 -06:00
grayson b48df32331 Add basic logging and error system 2025-01-28 16:53:15 -06:00
grayson 581db91157 Add versioning system 2025-01-23 16:57:55 -06:00
grayson 73445af2e7 Add precompiled header 2025-01-23 16:49:52 -06:00
grayson 9b6e333652 Add NothinFancy and TestGame 2025-01-23 16:42:47 -06:00
grayson af7d19a956 Add CMake project 2025-01-23 16:14:40 -06:00
27 changed files with 1328 additions and 0 deletions

No files matched your search

+1
View File
@@ -1 +1,2 @@
.vs/ .vs/
build/
+8
View File
@@ -0,0 +1,8 @@
# Main NF CMakeLists.txt
cmake_minimum_required(VERSION 3.20)
project(nf)
add_subdirectory(NothinFancy)
add_subdirectory(TestGame)
+49
View 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
View 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")
# Use C++20
set_property(TARGET NothinFancy PROPERTY CXX_STANDARD 20)
# Additional include directories
target_include_directories(NothinFancy PUBLIC "src" "src/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)
+9
View File
@@ -0,0 +1,9 @@
#version 450
layout(location = 0) in vec3 fragColor;
layout(location = 0) out vec4 outColor;
void main() {
outColor = vec4(fragColor, 1.0);
}
+20
View File
@@ -0,0 +1,20 @@
#version 450
vec2 trianglePositions[3] = vec2[] (
vec2(0.0, -0.5),
vec2(0.5, 0.5),
vec2(-0.5, 0.5)
);
vec3 triangleColors[3] = vec3[] (
vec3(1.0, 0.0, 0.0),
vec3(0.0, 1.0, 0.0),
vec3(0.0, 0.0, 1.0)
);
layout(location = 0) out vec3 fragColor;
void main() {
fragColor = triangleColors[gl_VertexIndex];
gl_Position = vec4(trianglePositions[gl_VertexIndex], 0.0, 1.0);
}
+34
View 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");
}
}
+39
View File
@@ -0,0 +1,39 @@
// 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);
while (m_running) {
m_renderEngine->doFrame();
}
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
View 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;
};
}
+127
View File
@@ -0,0 +1,127 @@
// 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()
{
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");
// 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;
config.width = windowWidth, config.height = windowHeight;
break;
}
}
SetWindowPos(m_handle, nullptr, windowX, windowY, windowWidth, windowHeight, SWP_NOZORDER | SWP_FRAMECHANGED);
}
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);
}
}
void Window::show(bool show) {
ShowWindow(m_handle, show ? SW_SHOW : SW_HIDE);
}
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");
}
}
+32
View File
@@ -0,0 +1,32 @@
// 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);
~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;
};
}
@@ -0,0 +1,522 @@
// RenderEngine class implementation
#include "pch.h"
#include "RenderEngine.h"
#include "util.h"
#include "ShaderModule.h"
namespace nf::client::render {
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_renderPassOutput()
, m_swapchain()
, m_swapchainImageFormat()
, m_swapchainImages()
, m_swapchainImageViews()
, m_swapchainFramebuffers()
, m_pipelineLayoutOutput()
, m_pipelineOutput()
, m_commandPool()
, m_numFramesInFlight(2)
, m_frameExecutors()
, m_currentFrameExecutor()
{
NFLog("Initializing render engine");
m_window->setDisplay(m_display);
createInstance();
createSurface();
pickPhysicalDevice();
createDevice();
createSwapchain();
createOutputRenderPass();
createOutputPipeline();
createFrameExecutors();
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;
for (int i = 0; i < numPhysicalDevices; i++) {
VkPhysicalDeviceProperties currentPhysicalDeviceProperties = {};
vkGetPhysicalDeviceProperties(physicalDevices[i], &currentPhysicalDeviceProperties);
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;
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];
VkExtent2D swapchainExtent = surfaceCapabilities.currentExtent;
if (swapchainExtent.width == std::numeric_limits<uint32_t>::max())
swapchainExtent.width = m_display.width, swapchainExtent.height = 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 = 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::createOutputRenderPass() {
VkAttachmentDescription outputColorAttachment = {};
outputColorAttachment.format = m_swapchainImageFormat;
outputColorAttachment.samples = VK_SAMPLE_COUNT_1_BIT;
outputColorAttachment.loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR;
outputColorAttachment.storeOp = VK_ATTACHMENT_STORE_OP_STORE;
outputColorAttachment.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE;
outputColorAttachment.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
outputColorAttachment.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
outputColorAttachment.finalLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR;
VkAttachmentReference outputColorAttachmentReference = {};
outputColorAttachmentReference.attachment = 0;
outputColorAttachmentReference.layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
VkSubpassDescription subpassDescription = {};
subpassDescription.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS;
subpassDescription.colorAttachmentCount = 1;
subpassDescription.pColorAttachments = &outputColorAttachmentReference;
VkSubpassDependency dependency = {};
dependency.srcSubpass = VK_SUBPASS_EXTERNAL;
dependency.srcStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
dependency.dstStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
dependency.dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT;
VkRenderPassCreateInfo renderPassCI = { VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO };
renderPassCI.attachmentCount = 1;
renderPassCI.pAttachments = &outputColorAttachment;
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
VkDynamicState dynamicState = VK_DYNAMIC_STATE_VIEWPORT;
VkPipelineDynamicStateCreateInfo dynamicStateCI = { VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO };
dynamicStateCI.dynamicStateCount = 1;
dynamicStateCI.pDynamicStates = &dynamicState;
// Specify vertex input state
VkPipelineVertexInputStateCreateInfo vertexInputStateCI = { VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO };
// 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
VkRect2D scissor = { {0, 0}, {m_display.width, m_display.height} };
VkPipelineViewportStateCreateInfo viewportStateCI = { VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO };
viewportStateCI.viewportCount = 1;
viewportStateCI.scissorCount = 1;
viewportStateCI.pScissors = &scissor;
// 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_CLOCKWISE;
// Specify multisample state
VkPipelineMultisampleStateCreateInfo multisampleStateCI = { VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO };
multisampleStateCI.rasterizationSamples = VK_SAMPLE_COUNT_1_BIT;
// 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 pipeline layout
VkPipelineLayoutCreateInfo layoutCI = { VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO };
if (vkCreatePipelineLayout(m_device, &layoutCI, nullptr, &m_pipelineLayoutOutput) != VK_SUCCESS)
NFError("Could not create pipeline layout.");
// 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.pColorBlendState = &colorBlendStateCI;
pipelineCI.pDynamicState = &dynamicStateCI;
pipelineCI.layout = m_pipelineLayoutOutput;
pipelineCI.renderPass = m_renderPassOutput;
if (vkCreateGraphicsPipelines(m_device, nullptr, 1, &pipelineCI, nullptr, &m_pipelineOutput) != VK_SUCCESS)
NFError("Could not create graphics pipeline.");
// TODO: For now, create swapchain framebuffers
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;
framebufferCI.attachmentCount = 1;
framebufferCI.pAttachments = &m_swapchainImageViews[i];
framebufferCI.width = m_display.width, framebufferCI.height = m_display.height;
framebufferCI.layers = 1;
if (vkCreateFramebuffer(m_device, &framebufferCI, nullptr, &m_swapchainFramebuffers[i]) != VK_SUCCESS)
NFError("Could not create framebuffer.");
}
}
void RenderEngine::createFrameExecutors() {
VkCommandPoolCreateInfo commandPoolCI = { VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO };
commandPoolCI.flags = VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT;
commandPoolCI.queueFamilyIndex = m_queueFIGraphics;
if (vkCreateCommandPool(m_device, &commandPoolCI, nullptr, &m_commandPool) != VK_SUCCESS)
NFError("Could not create command pool.");
m_frameExecutors.resize(m_numFramesInFlight);
for (int i = 0; i < m_frameExecutors.size(); i++) {
VkCommandBufferAllocateInfo commandBufferAI = { VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO };
commandBufferAI.commandPool = m_commandPool;
commandBufferAI.commandBufferCount = 1;
if (vkAllocateCommandBuffers(m_device, &commandBufferAI, &m_frameExecutors[i].commandBuffer) != VK_SUCCESS)
NFError("Could not create command buffer.");
VkSemaphoreCreateInfo semaphoreCI = { VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO };
if (vkCreateSemaphore(m_device, &semaphoreCI, nullptr, &m_frameExecutors[i].semaphoreImageAvailable) != VK_SUCCESS ||
vkCreateSemaphore(m_device, &semaphoreCI, nullptr, &m_frameExecutors[i].semaphoreRenderFinished) != 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_frameExecutors[i].fenceFrameInFlight) != VK_SUCCESS)
NFError("Could not create fence.");
}
}
void RenderEngine::doFrame() {
// Get current frame executor
auto& [commandBuffer, semaphoreImageAvailable, semaphoreRenderFinished, fenceFrameInFlight] = m_frameExecutors[m_currentFrameExecutor];
// First, wait for previous frame to complete
vkWaitForFences(m_device, 1, &fenceFrameInFlight, VK_TRUE, UINT64_MAX);
vkResetFences(m_device, 1, &fenceFrameInFlight);
// Get next swapchain image
uint32_t nextImageIndex = 0;
vkAcquireNextImageKHR(m_device, m_swapchain, UINT64_MAX, semaphoreImageAvailable, nullptr, &nextImageIndex);
// Begin recording
VkCommandBufferBeginInfo commandBufferBI = { VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO };
if (vkBeginCommandBuffer(commandBuffer, &commandBufferBI) != VK_SUCCESS)
NFError("Could not begin recording command buffer.");
// Start the render pass
VkClearValue black = { {{0.0, 0.0, 0.0, 1.0}} };
VkRenderPassBeginInfo renderPassBI = { VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO };
renderPassBI.renderPass = m_renderPassOutput;
renderPassBI.framebuffer = m_swapchainFramebuffers[nextImageIndex];
renderPassBI.renderArea.extent = { m_display.width, m_display.height };
renderPassBI.clearValueCount = 1;
renderPassBI.pClearValues = &black;
vkCmdBeginRenderPass(commandBuffer, &renderPassBI, VK_SUBPASS_CONTENTS_INLINE);
// Bind output pipeline
vkCmdBindPipeline(commandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, m_pipelineOutput);
// Set viewport
VkViewport viewport = {};
viewport.width = static_cast<float>(m_display.width), viewport.height = static_cast<float>(m_display.height);
viewport.maxDepth = 1.0;
vkCmdSetViewport(commandBuffer, 0, 1, &viewport);
// Draw
vkCmdDraw(commandBuffer, 3, 1, 0, 0);
// End the render pass
vkCmdEndRenderPass(commandBuffer);
// Finish recording
if (vkEndCommandBuffer(commandBuffer) != VK_SUCCESS)
NFError("Could not end command buffer.");
// 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 = &semaphoreImageAvailable;
submitInfo.pWaitDstStageMask = &waitStage;
submitInfo.commandBufferCount = 1;
submitInfo.pCommandBuffers = &commandBuffer;
submitInfo.signalSemaphoreCount = 1;
submitInfo.pSignalSemaphores = &semaphoreRenderFinished;
if (vkQueueSubmit(m_queueGraphics, 1, &submitInfo, 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 = &semaphoreRenderFinished;
presentInfo.swapchainCount = 1;
presentInfo.pSwapchains = &m_swapchain;
presentInfo.pImageIndices = &nextImageIndex;
vkQueuePresentKHR(m_queuePresent, &presentInfo);
// Advance to next frame executor
m_currentFrameExecutor = (m_currentFrameExecutor + 1) % m_numFramesInFlight;
}
RenderEngine::~RenderEngine() {
vkDeviceWaitIdle(m_device);
for (int i = 0; i < m_frameExecutors.size(); i++) {
vkDestroyFence(m_device, m_frameExecutors[i].fenceFrameInFlight, nullptr);
vkDestroySemaphore(m_device, m_frameExecutors[i].semaphoreRenderFinished, nullptr);
vkDestroySemaphore(m_device, m_frameExecutors[i].semaphoreImageAvailable, nullptr);
}
vkDestroyCommandPool(m_device, m_commandPool, nullptr);
for (int i = 0; i < m_swapchainFramebuffers.size(); i++)
vkDestroyFramebuffer(m_device, m_swapchainFramebuffers[i], nullptr);
vkDestroyPipeline(m_device, m_pipelineOutput, nullptr);
vkDestroyPipelineLayout(m_device, m_pipelineLayoutOutput, nullptr);
vkDestroyRenderPass(m_device, m_renderPassOutput, nullptr);
for (int i = 0; i < m_swapchainImageViews.size(); i++)
vkDestroyImageView(m_device, m_swapchainImageViews[i], nullptr);
vkDestroySwapchainKHR(m_device, m_swapchain, nullptr);
vkDestroyDevice(m_device, nullptr);
vkDestroySurfaceKHR(m_instance, m_surface, nullptr);
vkDestroyInstance(m_instance, nullptr);
}
}
@@ -0,0 +1,60 @@
// RenderEngine class header
#pragma once
#include "client/Window.h"
#include "nf/config.h"
namespace nf::client::render {
struct FrameExecutor {
VkCommandBuffer commandBuffer;
VkSemaphore semaphoreImageAvailable, semaphoreRenderFinished;
VkFence fenceFrameInFlight;
};
class RenderEngine {
public:
RenderEngine(std::shared_ptr<Window> window, DisplayConfig display);
void doFrame();
~RenderEngine();
private:
void createInstance();
void createSurface();
void pickPhysicalDevice();
void createDevice();
void createSwapchain();
void createOutputRenderPass();
void createOutputPipeline();
void createFrameExecutors();
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;
VkRenderPass m_renderPassOutput;
// Swapchain
VkSwapchainKHR m_swapchain;
VkFormat m_swapchainImageFormat;
std::vector<VkImage> m_swapchainImages;
std::vector<VkImageView> m_swapchainImageViews;
std::vector<VkFramebuffer> m_swapchainFramebuffers;
// Pipelines
VkPipelineLayout m_pipelineLayoutOutput;
VkPipeline m_pipelineOutput;
// Execution objects
VkCommandPool m_commandPool;
const uint32_t m_numFramesInFlight;
std::vector<FrameExecutor> m_frameExecutors;
uint32_t m_currentFrameExecutor;
};
}
@@ -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)
: m_device(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);
}
}
@@ -0,0 +1,17 @@
// ShaderModule class header
#pragma once
namespace nf::client::render {
class ShaderModule {
public:
ShaderModule(const VkDevice& device, const std::string& shaderBinary);
VkShaderModule& getHandle();
~ShaderModule();
private:
const VkDevice& m_device;
VkShaderModule m_shaderModule;
};
}
+28
View 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
View 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()
{}
};
}
+44
View File
@@ -0,0 +1,44 @@
// 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>
+26
View 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
View 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
View 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
View 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
View 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
View 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
View File
@@ -0,0 +1,2 @@
// Version configured header
#define NFVERSION "@NFVERSION@"
+23
View 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
View 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;
}
}