Shaders assets and loading fix

- a = { ... } definitions replaced with a { ... }
- GetOverlappedResult replaced with waiting for event in Windows async file loading
- FileHandles and AssetHandles stored in heap for container element adresses consistence
- Shader assets examples
This commit is contained in:
legatt 2025-08-24 14:56:17 +03:00
parent 9a258f7443
commit 7ad2ec4dc8
13 changed files with 182 additions and 77 deletions

View File

@ -11,12 +11,12 @@ struct WorldBenchmark : testing::Test
{ {
for (int16_t i = 0; i < 1000; i++) for (int16_t i = 0; i < 1000; i++)
{ {
ee_economy::Container resources = {.capacity = 100.0}; ee_economy::Container resources {.capacity = 100.0};
resources.add(ee_economy::RESOURCE_TYPE::COAL, 100.0); resources.add(ee_economy::RESOURCE_TYPE::COAL, 100.0);
entt::entity state = world.registerState(i, resources); entt::entity state = world.registerState(i, resources);
ee_economy::EnterpriseDefaults miner = { ee_economy::EnterpriseDefaults miner {
.container = {.capacity = 100.0}, .container {.capacity = 100.0},
.resourceTarget = ee_economy::RESOURCE_TYPE::GOLD, .resourceTarget = ee_economy::RESOURCE_TYPE::GOLD,
.baseEfficiency = 1.0 .baseEfficiency = 1.0
}; };

View File

@ -7,7 +7,7 @@ TEST(WorldTest, RegisterState)
{ {
ee_ecs::World world; ee_ecs::World world;
ee_economy::Container res = {.capacity = 100.0}; ee_economy::Container res {.capacity = 100.0};
res.add(ee_economy::RESOURCE_TYPE::COAL, 98.0); res.add(ee_economy::RESOURCE_TYPE::COAL, 98.0);
entt::entity state = world.registerState(10, res); entt::entity state = world.registerState(10, res);
@ -37,8 +37,8 @@ TEST(WorldTest, SpawnMiner)
world.getRegistry().emplace<ee_economy::State>(state, (int16_t)0); world.getRegistry().emplace<ee_economy::State>(state, (int16_t)0);
world.getRegistry().emplace<ee_economy::Container>(state, 100.0); world.getRegistry().emplace<ee_economy::Container>(state, 100.0);
ee_economy::EnterpriseDefaults defaults = { ee_economy::EnterpriseDefaults defaults {
.container = {.capacity = 100.0 }, .container {.capacity = 100.0 },
.resourceTarget = ee_economy::RESOURCE_TYPE::COAL, .resourceTarget = ee_economy::RESOURCE_TYPE::COAL,
.baseEfficiency = 1.0, .baseEfficiency = 1.0,
}; };
@ -62,8 +62,8 @@ TEST(WorldTest, SpawnDeliverer)
world.getRegistry().emplace<ee_economy::State>(state, (int16_t)0); world.getRegistry().emplace<ee_economy::State>(state, (int16_t)0);
world.getRegistry().emplace<ee_economy::Container>(state, 100.0); world.getRegistry().emplace<ee_economy::Container>(state, 100.0);
ee_economy::EnterpriseDefaults defaults = { ee_economy::EnterpriseDefaults defaults {
.container = {.capacity = 100.0 }, .container {.capacity = 100.0 },
.resourceTarget = ee_economy::RESOURCE_TYPE::COAL, .resourceTarget = ee_economy::RESOURCE_TYPE::COAL,
.baseEfficiency = 1.0, .baseEfficiency = 1.0,
}; };

Binary file not shown.

Binary file not shown.

View File

@ -12,4 +12,5 @@ struct AssetBinary : AssetData
AssetBinary(uint8_t* &source, size_t sourceSize); AssetBinary(uint8_t* &source, size_t sourceSize);
~AssetBinary(); ~AssetBinary();
}; };
typedef AssetBinary AssetShader;

View File

@ -27,6 +27,7 @@ public:
BINARY, BINARY,
SHADER, SHADER,
MATERIAL,
IMAGE IMAGE
} type = BINARY; } type = BINARY;
@ -82,6 +83,8 @@ public:
void Request(const AssetStreamCommand &command); void Request(const AssetStreamCommand &command);
void Request(const AssetStreamCommand::Type &type); void Request(const AssetStreamCommand::Type &type);
void LoadShaders();
void LoadAsync(AssetInfo::Type type, const std::filesystem::path &path); void LoadAsync(AssetInfo::Type type, const std::filesystem::path &path);
AssetHandle& Find(const std::filesystem::path &path); AssetHandle& Find(const std::filesystem::path &path);
void Redirect(AssetHandle &handle, void *resource); void Redirect(AssetHandle &handle, void *resource);
@ -96,7 +99,7 @@ private:
IFileSystem *diskSystem; IFileSystem *diskSystem;
std::vector<AssetInfo> activeAssets; std::vector<AssetInfo> activeAssets;
std::unordered_map<std::filesystem::path, AssetHandle> assetsMapping; std::unordered_map<std::filesystem::path, AssetHandle*> assetsMapping;
std::vector<size_t> freeAssetSlots; std::vector<size_t> freeAssetSlots;
std::vector<AssetLoadingEntry> loadingEntries; std::vector<AssetLoadingEntry> loadingEntries;
@ -111,7 +114,7 @@ private:
template <Asset T> T& AssetManager::Get(const AssetHandle &handle) template <Asset T> T& AssetManager::Get(const AssetHandle &handle)
{ {
std::atomic<void*> result; std::atomic<void*> result;
AssetStreamCommand command = { AssetStreamCommand command {
.type = AssetStreamCommand::GET, .type = AssetStreamCommand::GET,
.target = handle, .target = handle,
.out = &result .out = &result

View File

@ -46,11 +46,13 @@ public:
virtual FileHandle& Open(const std::filesystem::path &path, const uint32_t flags) = 0; virtual FileHandle& Open(const std::filesystem::path &path, const uint32_t flags) = 0;
virtual void ReadAsync(FileHandle &file, const uint32_t bufferSize, const uint32_t flags) = 0; virtual void ReadAsync(FileHandle &file, const uint32_t bufferSize, const uint32_t flags) = 0;
virtual bool PollReady(FileHandle &file) = 0; virtual bool PollReady(FileHandle &file) = 0;
virtual void Close(const FileHandle &file) = 0; virtual void Close(FileHandle &file) = 0;
~IFileSystem();
protected: protected:
std::vector<FileHandle> activeFiles; std::vector<FileHandle*> activeFiles;
std::vector<FileHandle> failedFiles; std::vector<FileHandle*> failedFiles;
std::vector<size_t> freeFileSlots; std::vector<size_t> freeFileSlots;
}; };
@ -60,7 +62,7 @@ public:
FileHandle& Open(const std::filesystem::path &path, const uint32_t flags) override; FileHandle& Open(const std::filesystem::path &path, const uint32_t flags) override;
void ReadAsync(FileHandle &file, const uint32_t bufferSize, uint32_t flags) override; void ReadAsync(FileHandle &file, const uint32_t bufferSize, uint32_t flags) override;
bool PollReady(FileHandle &file) override; bool PollReady(FileHandle &file) override;
void Close(const FileHandle &file) override; void Close(FileHandle &file) override;
private: private:

View File

@ -1,6 +1,7 @@
#include "AssetManager.hpp" #include "AssetManager.hpp"
#include "allocator.hpp" #include "allocator.hpp"
#include "Logger.h" #include "Logger.h"
#include <stdexcept>
AssetInfo::AssetInfo(void *resource) AssetInfo::AssetInfo(void *resource)
{ {
@ -81,7 +82,7 @@ void AssetManager::Request(const AssetStreamCommand::Type &type)
void AssetManager::LoadAsync(AssetInfo::Type type, const std::filesystem::path &path) void AssetManager::LoadAsync(AssetInfo::Type type, const std::filesystem::path &path)
{ {
AssetStreamCommand command = { AssetStreamCommand command {
.type = AssetStreamCommand::LOAD, .type = AssetStreamCommand::LOAD,
.targetPath = path, .targetPath = path,
.targetType = type .targetType = type
@ -92,7 +93,7 @@ void AssetManager::LoadAsync(AssetInfo::Type type, const std::filesystem::path &
AssetHandle& AssetManager::Find(const std::filesystem::path &path) AssetHandle& AssetManager::Find(const std::filesystem::path &path)
{ {
std::atomic<void*> result; std::atomic<void*> result;
AssetStreamCommand command = { AssetStreamCommand command {
.type = AssetStreamCommand::FIND, .type = AssetStreamCommand::FIND,
.targetPath = path, .targetPath = path,
.out = &result .out = &result
@ -104,7 +105,7 @@ AssetHandle& AssetManager::Find(const std::filesystem::path &path)
void AssetManager::Start() void AssetManager::Start()
{ {
if (isRunning) throw std::exception("Asset Streaming can be started only once"); if (isRunning) throw std::logic_error("Asset Streaming can be started only once");
isRunning.store(true); isRunning.store(true);
streamingThread = std::jthread([this](std::stop_token stop) { streamingThread = std::jthread([this](std::stop_token stop) {
@ -156,7 +157,7 @@ void AssetManager::Loop()
//TODO: functions for each type //TODO: functions for each type
switch (asset.type) switch (asset.type)
{ {
case AssetInfo::SHADER: case AssetInfo::MATERIAL:
{ {
break; break;
} }
@ -166,6 +167,7 @@ void AssetManager::Loop()
break; break;
} }
case AssetInfo::SHADER:
case AssetInfo::BINARY: case AssetInfo::BINARY:
default: default:
{ {
@ -176,8 +178,13 @@ void AssetManager::Loop()
} }
} }
asset.isReady.store(true); entry.fileOwner->Close(*entry.file);
#ifndef NDEBUG
auto path = entry.file->debugPath.c_str();
LOG(INFO, "Asset loaded: [%s]", path);
#endif
entry.file = nullptr; entry.file = nullptr;
asset.isReady.store(true);
} }
} }
@ -192,7 +199,7 @@ void AssetManager::ProcessCommand(AssetStreamCommand &command)
{ {
case AssetStreamCommand::LOAD: case AssetStreamCommand::LOAD:
{ {
LOG(INFO, "LOAD command"); LOG(INFO, "LOAD command: [%s]", command.targetPath.c_str());
/* TODO: process path to select FS */ /* TODO: process path to select FS */
/* TODO: select between async load and sync job task */ /* TODO: select between async load and sync job task */
@ -202,7 +209,7 @@ void AssetManager::ProcessCommand(AssetStreamCommand &command)
diskSystem->ReadAsync(file, static_cast<uint32_t>(file.size), FILE_READ_SEQUENTIAL); diskSystem->ReadAsync(file, static_cast<uint32_t>(file.size), FILE_READ_SEQUENTIAL);
AssetHandle handle; AssetHandle* handle = Allocate<AssetHandle>();
AssetInfo asset(nullptr); AssetInfo asset(nullptr);
asset.type = command.targetType; asset.type = command.targetType;
@ -218,11 +225,11 @@ void AssetManager::ProcessCommand(AssetStreamCommand &command)
activeAssets.push_back(std::move(asset)); activeAssets.push_back(std::move(asset));
assetPos = activeAssets.size() - 1; assetPos = activeAssets.size() - 1;
} }
handle.id = assetPos; handle->id = assetPos;
assetsMapping[command.targetPath] = handle; assetsMapping[command.targetPath] = handle;
AssetLoadingEntry loadingEntry = { AssetLoadingEntry loadingEntry {
.handle = handle, .handle = *handle,
.file = &file, .file = &file,
.fileOwner = diskSystem .fileOwner = diskSystem
}; };
@ -234,12 +241,12 @@ void AssetManager::ProcessCommand(AssetStreamCommand &command)
case AssetStreamCommand::FIND: case AssetStreamCommand::FIND:
{ {
std::unordered_map<std::filesystem::path, AssetHandle>::iterator mapping = assetsMapping.find(command.targetPath); std::unordered_map<std::filesystem::path, AssetHandle*>::iterator mapping = assetsMapping.find(command.targetPath);
if (mapping == assetsMapping.end()) if (mapping == assetsMapping.end())
break; break;
command.out->store(&mapping->second); command.out->store(mapping->second);
command.type = AssetStreamCommand::COMPLETED; command.type = AssetStreamCommand::COMPLETED;
break; break;
@ -280,6 +287,20 @@ void AssetManager::Release()
} }
activeAssets.clear(); activeAssets.clear();
for (auto &mapping : assetsMapping)
Deallocate(mapping.second);
assetsMapping.clear();
LOG(INFO, "Asset Streaming is released"); LOG(INFO, "Asset Streaming is released");
isRunning.store(false); isRunning.store(false);
}
void AssetManager::LoadShaders()
{
std::filesystem::path shadersRoot = "assets/shaders";
for (const std::filesystem::directory_entry &entry : std::filesystem::directory_iterator(shadersRoot))
{
if (entry.is_directory()) continue;
LoadAsync(AssetInfo::SHADER, entry.path());
}
} }

View File

@ -1,15 +1,24 @@
#include "FileSystem.hpp" #include "FileSystem.hpp"
#include "Logger.h" #include "Logger.h"
#include "allocator.hpp" #include "allocator.hpp"
#include <stdexcept>
IFileSystem::~IFileSystem()
{
for (FileHandle* handle : activeFiles)
Deallocate(handle);
for (FileHandle* handle : failedFiles)
Deallocate(handle);
}
#ifdef _WIN32 #ifdef _WIN32
FileHandle& NativeFileSystem::Open(const std::filesystem::path &path, const uint32_t flags) FileHandle& NativeFileSystem::Open(const std::filesystem::path &path, const uint32_t flags)
{ {
FileHandle result; FileHandle* result = Allocate<FileHandle>();
#ifndef NDEBUG #ifndef NDEBUG
result.debugPath = path; result->debugPath = path;
#endif #endif
DWORD createFileMode = 0; DWORD createFileMode = 0;
@ -33,78 +42,126 @@ FileHandle& NativeFileSystem::Open(const std::filesystem::path &path, const uint
{ {
LOG(ERROR, "Failed to open file %s. Error: %d", path.c_str(), GetLastError()); LOG(ERROR, "Failed to open file %s. Error: %d", path.c_str(), GetLastError());
failedFiles.push_back(result); failedFiles.push_back(result);
return failedFiles[result.id]; return *result;
} }
result.win32Handle = file; result->win32Handle = file;
result.size = GetFileSize(file, NULL); result->size = GetFileSize(file, NULL);
if (result.size == INVALID_FILE_SIZE) if (result->size == INVALID_FILE_SIZE)
LOG(ERROR, "Failed to get size of file %s. Error: %d", path.c_str(), GetLastError()); LOG(ERROR, "Failed to get size of file %s. Error: %d", path.c_str(), GetLastError());
if (freeFileSlots.size() > 0) if (freeFileSlots.size() > 0)
{ {
activeFiles[freeFileSlots.back()] = result; size_t pos = freeFileSlots.back();
result.id = freeFileSlots.back(); result->id = pos;
activeFiles[pos] = result;
freeFileSlots.pop_back(); freeFileSlots.pop_back();
} }
else else
{ {
result->id = activeFiles.size();
activeFiles.push_back(result); activeFiles.push_back(result);
result.id = activeFiles.size() - 1;
} }
return activeFiles[result.id]; return *result;
} }
void NativeFileSystem::ReadAsync(FileHandle &file, const uint32_t bufferSize, const uint32_t flags) void NativeFileSystem::ReadAsync(FileHandle &file, const uint32_t bufferSize, const uint32_t flags)
{ {
file.overlapped = {}; file.overlapped = {
.hEvent = CreateEvent(nullptr, TRUE, FALSE, nullptr)
};
file.buffer = AllocateArray<uint8_t>(bufferSize); file.buffer = AllocateArray<uint8_t>(bufferSize);
ReadFile( BOOL isSync = ReadFile(
file.win32Handle, file.win32Handle,
file.buffer, file.buffer,
bufferSize, bufferSize,
NULL, NULL,
&file.overlapped &file.overlapped
); );
if (isSync || GetLastError() != ERROR_IO_PENDING)
LOG(ERROR, "File reading is synchronious while asynchronious is awaited");
} }
bool NativeFileSystem::PollReady(FileHandle &file) bool NativeFileSystem::PollReady(FileHandle &file)
{ {
DWORD transferredSize = 0L; // DWORD transferredSize = 0L;
BOOL status = GetOverlappedResult( // BOOL status = GetOverlappedResult(
file.win32Handle, // file.win32Handle,
&file.overlapped, // &file.overlapped,
&transferredSize, // &transferredSize,
FALSE // FALSE
); // );
if (!status && GetLastError() != ERROR_IO_INCOMPLETE)
{ // DWORD error = GetLastError();
LOG(ERROR, "Error in file status. Code: %d", GetLastError()); // if (!status && error != ERROR_IO_INCOMPLETE)
return false; // {
} // LOG(CRITICAL, "Error in reading file status. Code: %d", error);
// throw std::runtime_error("Error in reading file status");
// }
if (file.size == transferredSize) // if (file.size == transferredSize)
{ // {
file.data = file.buffer; // file.data = file.buffer;
} // }
else // else
{ // {
/* TODO: reserve size for data, shift and move */ // /* TODO: reserve size for data, shift and move */
} // }
file.buffer = nullptr; // file.buffer = nullptr;
return status; DWORD status = WaitForSingleObject(file.overlapped.hEvent, 0);
switch (status)
{
case WAIT_OBJECT_0:
{
if (file.size == file.bufferSize)
{
file.data = file.buffer;
}
else
{
/* TODO: reserve size for data, shift and move */
}
break;
}
case WAIT_ABANDONED:
case WAIT_TIMEOUT:
return false;
default:
{
LOG(CRITICAL, "Failed to get file status. Error: %d", GetLastError());
throw std::runtime_error("Failed to get file status");
}
}
return true;
} }
void NativeFileSystem::Close(const FileHandle &file) void NativeFileSystem::Close(FileHandle &file)
{ {
DeallocateArray(file.buffer); if (file.buffer != nullptr)
DeallocateArray(file.data); {
DeallocateArray(file.buffer);
file.buffer = nullptr;
}
if (file.data != nullptr)
{
DeallocateArray(file.data);
file.data = nullptr;
}
CloseHandle(file.win32Handle); if (file.win32Handle != INVALID_HANDLE_VALUE)
activeFiles[file.id].win32Handle = INVALID_HANDLE_VALUE; {
CloseHandle(file.win32Handle);
file.win32Handle = INVALID_HANDLE_VALUE;
}
freeFileSlots.push_back(file.id); freeFileSlots.push_back(file.id);
} }

View File

@ -6,8 +6,8 @@
#include "Logger.h" #include "Logger.h"
#include <allocator.hpp> #include <allocator.hpp>
#include <algorithm> #include <algorithm>
#include <exception>
#include <set> #include <set>
#include <stdexcept>
Renderer::Renderer(SDL_Window *window) : isRunning(false) Renderer::Renderer(SDL_Window *window) : isRunning(false)
{ {
@ -228,7 +228,7 @@ void Renderer::SetupDevice()
if (deviceProperties.deviceType == VK_PHYSICAL_DEVICE_TYPE_DISCRETE_GPU) if (deviceProperties.deviceType == VK_PHYSICAL_DEVICE_TYPE_DISCRETE_GPU)
priority += GPU_DISCRETE_PRIORITY; priority += GPU_DISCRETE_PRIORITY;
for (VkSurfaceFormatKHR format : surfaceInfoBuffer.availableFormats) for (const VkSurfaceFormatKHR &format : surfaceInfoBuffer.availableFormats)
{ {
surfaceInfoBuffer.format = format; surfaceInfoBuffer.format = format;
if (format.format == VK_FORMAT_B8G8R8_SRGB && format.colorSpace == VK_COLOR_SPACE_SRGB_NONLINEAR_KHR) if (format.format == VK_FORMAT_B8G8R8_SRGB && format.colorSpace == VK_COLOR_SPACE_SRGB_NONLINEAR_KHR)
@ -239,7 +239,7 @@ void Renderer::SetupDevice()
} }
surfaceInfoBuffer.presentMode = VK_PRESENT_MODE_FIFO_KHR; surfaceInfoBuffer.presentMode = VK_PRESENT_MODE_FIFO_KHR;
for (VkPresentModeKHR presentMode : surfaceInfoBuffer.availablePresentModes) for (const VkPresentModeKHR &presentMode : surfaceInfoBuffer.availablePresentModes)
{ {
if (presentMode == VK_PRESENT_MODE_FIFO_RELAXED_KHR) if (presentMode == VK_PRESENT_MODE_FIFO_RELAXED_KHR)
{ {
@ -265,7 +265,7 @@ void Renderer::SetupDevice()
// Define queues // Define queues
std::vector<VkDeviceQueueCreateInfo> queuesCreateConfigs; std::vector<VkDeviceQueueCreateInfo> queuesCreateConfigs;
std::set<uint32_t> uniqueQueueFamilies = { std::set<uint32_t> uniqueQueueFamilies {
queueFamilies.graphics, queueFamilies.graphics,
queueFamilies.present queueFamilies.present
}; };
@ -284,11 +284,11 @@ void Renderer::SetupDevice()
} }
// Create logical device // Create logical device
std::vector<const char*> deviceExtensions = { std::vector<const char*> deviceExtensions {
VK_KHR_SWAPCHAIN_EXTENSION_NAME VK_KHR_SWAPCHAIN_EXTENSION_NAME
}; };
VkDeviceCreateInfo deviceInfo = { VkDeviceCreateInfo deviceInfo {
.sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO, .sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO,
.pNext = nullptr, .pNext = nullptr,
.queueCreateInfoCount = static_cast<uint32_t>(queuesCreateConfigs.size()), .queueCreateInfoCount = static_cast<uint32_t>(queuesCreateConfigs.size()),
@ -355,7 +355,7 @@ void Renderer::SetupSwapchain()
if (queueFamilies.graphics != queueFamilies.present) if (queueFamilies.graphics != queueFamilies.present)
{ {
uint32_t queueFamiliesShare[] = {queueFamilies.graphics, queueFamilies.present}; uint32_t queueFamiliesShare[] {queueFamilies.graphics, queueFamilies.present};
swapchainCreateConfig.imageSharingMode = VK_SHARING_MODE_CONCURRENT; swapchainCreateConfig.imageSharingMode = VK_SHARING_MODE_CONCURRENT;
swapchainCreateConfig.queueFamilyIndexCount = 2; swapchainCreateConfig.queueFamilyIndexCount = 2;
swapchainCreateConfig.pQueueFamilyIndices = queueFamiliesShare; swapchainCreateConfig.pQueueFamilyIndices = queueFamiliesShare;
@ -386,13 +386,13 @@ void Renderer::SetupSwapchain()
.image = frames[i], .image = frames[i],
.viewType = VK_IMAGE_VIEW_TYPE_2D, .viewType = VK_IMAGE_VIEW_TYPE_2D,
.format = surfaceInfo.format.format, .format = surfaceInfo.format.format,
.components = { .components {
.r = VK_COMPONENT_SWIZZLE_IDENTITY, .r = VK_COMPONENT_SWIZZLE_IDENTITY,
.g = VK_COMPONENT_SWIZZLE_IDENTITY, .g = VK_COMPONENT_SWIZZLE_IDENTITY,
.b = VK_COMPONENT_SWIZZLE_IDENTITY, .b = VK_COMPONENT_SWIZZLE_IDENTITY,
.a = VK_COMPONENT_SWIZZLE_IDENTITY .a = VK_COMPONENT_SWIZZLE_IDENTITY
}, },
.subresourceRange = { .subresourceRange {
.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT, .aspectMask = VK_IMAGE_ASPECT_COLOR_BIT,
.baseMipLevel = 0, .baseMipLevel = 0,
.levelCount = 1, .levelCount = 1,
@ -413,7 +413,7 @@ void Renderer::Start()
if (isRunning) if (isRunning)
{ {
LOG(CRITICAL, "Renderer can be started only once"); LOG(CRITICAL, "Renderer can be started only once");
throw std::exception("Renderer can be started only once"); throw std::logic_error("Renderer can be started only once");
} }
isRunning.store(true); isRunning.store(true);
@ -493,7 +493,7 @@ void Renderer::Request(const RenderCommand::Type &type)
void Renderer::Release() void Renderer::Release()
{ {
for (VkImageView frameView : frameViews) for (VkImageView &frameView : frameViews)
{ {
vkDestroyImageView(device, frameView, nullptr); vkDestroyImageView(device, frameView, nullptr);
} }

View File

@ -43,6 +43,7 @@ int SDL_main(int argc, char **argv)
// Setup assets // Setup assets
AssetManager assetManager; AssetManager assetManager;
assetManager.Start(); assetManager.Start();
assetManager.LoadShaders();
// Setup Renderer // Setup Renderer
Renderer renderer(window); Renderer renderer(window);

View File

@ -0,0 +1,8 @@
#version 330
#extension GL_ARB_separate_shader_objects : enable
layout(location = 0) out vec4 outColor;
void main() {
outColor = vec4(1.0, 0.0, 0.0, 1.0);
}

View File

@ -0,0 +1,12 @@
#version 330
vec2 positions[3] = vec2[](
vec2(-1.0, 1.0),
vec2(0.0, -1.0),
vec2(1.0, 1.0)
);
void main()
{
gl_Position = vec4(positions[gl_VertexIndex], 0.0, 1.0);
}