This commit is contained in:
2026-09-06 23:40:49 -07:00
parent 9b1e7d56a3
commit b6a2d5bf11
6 changed files with 423 additions and 119 deletions
-82
View File
@@ -1,82 +0,0 @@
#include "engine.hpp"
#include <chrono>
#include <sophia/frame_info.hpp>
#include <sophia/util/logger.hpp>
#include <vulkan/vulkan.hpp>
namespace sophia {
Engine::Engine(const Config& config) : config{config} {
if (config.headless) {
this->device = Device::Builder().headless().build();
} else {
this->window =
std::make_unique<Window>(config.width, config.height, config.name);
this->device = Device::Builder().withWindow(*this->window).build();
}
// this->imguiDescriptorPool =
// DescriptorPool::Builder(this->device)
// .addPoolSize(vk::DescriptorType::eCombinedImageSampler,
// IMGUI_IMPL_VULKAN_MINIMUM_IMAGE_SAMPLER_POOL_SIZE
// + 5)
// .setPoolFlags(vk::DescriptorPoolCreateFlagBits::eFreeDescriptorSet)
// .setMaxSets(IMGUI_IMPL_VULKAN_MINIMUM_IMAGE_SAMPLER_POOL_SIZE
// + 5)
// .build();
// this->uiManager =
// UIManager::Builder(this->window, this->device, this->renderer,
// imguiDescriptorPool->get())
// .darkTheme()
// .setDocking(true)
// .setKeyboard(true)
// .build();
};
Engine::~Engine() { this->device->waitIdle(); };
void Engine::run() {
auto startTime = std::chrono::high_resolution_clock::now();
auto currentTime = startTime;
// EventSystem::get().addListener<KeyReleasedEvent>(
// std::bind(&Application::onEvent, this, std::placeholders::_1));
while (this->isRunning) {
if (this->window) {
glfwPollEvents();
}
auto newTime = std::chrono::high_resolution_clock::now();
double deltaTime =
std::chrono::duration<double>(newTime - currentTime).count();
currentTime = newTime;
// // Attempt to start a new frame
// vk::CommandBuffer commandBuffer = this->renderer.beginFrame();
// if (commandBuffer != nullptr) {
// double elapsedTime =
// std::chrono::duration<double>(currentTime - startTime).count();
// vk::Extent2D extent = this->renderer.getCurrentFramebufferExtent();
// glm::vec2 extentVec2(static_cast<float>(extent.width),
// static_cast<float>(extent.height));
// FrameInfo frameInfo{this->renderer.getFrameIndex(), elapsedTime,
// deltaTime, extentVec2};
// }
this->isRunning = false;
}
this->device->waitIdle();
auto endTime = std::chrono::high_resolution_clock::now();
double totalRuntime =
std::chrono::duration<double>(endTime - startTime).count();
log::info("Application ran for", totalRuntime, "s");
}
} // namespace sophia
-37
View File
@@ -1,37 +0,0 @@
#pragma once
#include <memory>
#include <string>
#include "device.hpp"
#include "window.hpp"
namespace sophia {
class Engine {
public:
struct Config {
int width;
int height;
std::string name;
bool headless = false;
};
Engine(const Engine&) = delete;
Engine& operator=(const Engine&) = delete;
Engine(const Config& config);
~Engine();
void run();
private:
Config config;
std::unique_ptr<Window> window;
std::unique_ptr<Device> device;
bool isRunning = true;
};
} // namespace sophia
+124
View File
@@ -0,0 +1,124 @@
#include "model.hpp"
#include <cassert>
#include <sophia/util/logger.hpp>
namespace sophia {
std::vector<vk::VertexInputBindingDescription>
Model::Vertex::getBindingDescriptions() {
std::vector<vk::VertexInputBindingDescription> bindingDescriptions(1);
bindingDescriptions[0].binding = 0;
bindingDescriptions[0].stride = sizeof(Vertex);
bindingDescriptions[0].inputRate = vk::VertexInputRate::eVertex;
return bindingDescriptions;
}
std::vector<vk::VertexInputAttributeDescription>
Model::Vertex::getAttributeDescriptions() {
std::vector<vk::VertexInputAttributeDescription> attributeDescriptions{};
attributeDescriptions.push_back(
{0, 0, vk::Format::eR32G32Sfloat, offsetof(Vertex, position)});
attributeDescriptions.push_back(
{1, 0, vk::Format::eR32G32B32Sfloat, offsetof(Vertex, color)});
// attributeDescriptions.push_back(
// {2, 0, vk::Format::eR32G32B32Sfloat, offsetof(Vertex, normal)});
// attributeDescriptions.push_back(
// {3, 0, vk::Format::eR32G32Sfloat, offsetof(Vertex, uv)});
return attributeDescriptions;
}
Model::Model(Device& device, const Builder& builder) : device{device} {
createVertexBuffers(builder.vertices);
createIndexBuffers(builder.indices);
}
Model::~Model() {}
void Model::bind(vk::CommandBuffer commandBuffer) {
vk::Buffer buffers[] = {this->vertexBuffer->getBuffer()};
vk::DeviceSize offset[] = {0};
commandBuffer.bindVertexBuffers(0, 1, buffers, offset);
if (this->hasIndexBuffer) {
commandBuffer.bindIndexBuffer(this->indexBuffer->getBuffer(), 0,
vk::IndexType::eUint32);
}
}
void Model::draw(vk::CommandBuffer commandBuffer) {
if (this->hasIndexBuffer) {
commandBuffer.drawIndexed(this->indexCount, 1, 0, 0, 0);
} else {
commandBuffer.draw(this->vertexCount, 1, 0, 0);
}
}
void Model::createVertexBuffers(const std::vector<Vertex>& vertices) {
this->vertexCount = static_cast<u32>(vertices.size());
assert(this->vertexCount >= 3 && "vertex count must be at least 3");
vk::DeviceSize bufferSize = sizeof(vertices[0]) * this->vertexCount;
u32 vertexSize = sizeof(vertices[0]);
Buffer stagingBuffer{
this->device,
vertexSize,
vertexCount,
vk::BufferUsageFlagBits::eTransferSrc,
vk::MemoryPropertyFlagBits::eHostVisible |
vk::MemoryPropertyFlagBits::eHostCoherent,
};
stagingBuffer.map();
stagingBuffer.writeToBuffer((void*)vertices.data());
vertexBuffer =
std::make_unique<Buffer>(this->device, vertexSize, vertexCount,
vk::BufferUsageFlagBits::eTransferDst |
vk::BufferUsageFlagBits::eVertexBuffer,
vk::MemoryPropertyFlagBits::eDeviceLocal);
this->device.copyBuffer(stagingBuffer.getBuffer(),
this->vertexBuffer->getBuffer(), bufferSize);
}
void Model::createIndexBuffers(const std::vector<u32>& indicies) {
this->indexCount = static_cast<u32>(indicies.size());
this->hasIndexBuffer = this->indexCount > 0;
if (!this->hasIndexBuffer) {
return;
}
vk::DeviceSize bufferSize = sizeof(indicies[0]) * this->indexCount;
u32 indexSize = sizeof(indicies[0]);
Buffer stagingBuffer{
this->device,
indexSize,
indexCount,
vk::BufferUsageFlagBits::eTransferSrc,
vk::MemoryPropertyFlagBits::eHostVisible |
vk::MemoryPropertyFlagBits::eHostCoherent,
};
stagingBuffer.map();
stagingBuffer.writeToBuffer((void*)indicies.data());
indexBuffer =
std::make_unique<Buffer>(this->device, indexSize, indexCount,
vk::BufferUsageFlagBits::eTransferDst |
vk::BufferUsageFlagBits::eIndexBuffer,
vk::MemoryPropertyFlagBits::eDeviceLocal);
this->device.copyBuffer(stagingBuffer.getBuffer(),
this->indexBuffer->getBuffer(), bufferSize);
}
} // namespace sophia
+66
View File
@@ -0,0 +1,66 @@
#pragma once
#include <memory>
#include <vector>
#define GLM_FORCE_RADIANS
#define GLM_FORCE_DEPTH_ZERO_TO_ONE
#include <glm/glm.hpp>
#include "buffer.hpp"
#include "device.hpp"
namespace sophia {
class Model {
public:
struct Vertex {
glm::vec2 position{};
glm::vec3 color{};
// glm::vec3 normal{};
// glm::vec2 uv{};
static std::vector<vk::VertexInputBindingDescription>
getBindingDescriptions();
static std::vector<vk::VertexInputAttributeDescription>
getAttributeDescriptions();
// bool operator==(const Vertex& other) const {
// return position == other.position && color == other.color;
// }
};
struct Builder {
std::vector<Vertex> vertices{};
std::vector<uint32_t> indices{};
// void loadModel(const std::string& filepath);
};
Model(Device& device, const Model::Builder& builder);
~Model();
Model(const Model&) = delete;
Model& operator=(const Model&) = delete;
// static std::unique_ptr<Model> createModelFromFile(
// Device& device, const std::string& filepath);
void bind(vk::CommandBuffer commandBuffer);
void draw(vk::CommandBuffer commandBuffer);
private:
void createVertexBuffers(const std::vector<Vertex>& vertices);
void createIndexBuffers(const std::vector<u32>& indices);
Device& device;
std::unique_ptr<Buffer> vertexBuffer;
u32 vertexCount;
bool hasIndexBuffer = false;
std::unique_ptr<Buffer> indexBuffer;
u32 indexCount;
};
} // namespace sophia
+165
View File
@@ -0,0 +1,165 @@
#include "render_context.hpp"
#include <cassert>
#include <limits>
#include <sophia/util/logger.hpp>
#include <stdexcept>
namespace sophia {
RenderContext::RenderContext(Window& window, Device& device)
: window{window}, device{device} {
createFrames();
recreateSwapchain();
}
RenderContext::~RenderContext() { this->device.waitIdle(); }
vk::CommandBuffer RenderContext::beginFrame() {
assert(!this->isFrameStarted &&
"Can't call beginFrame while already in progress");
FrameData& frame = this->frames[this->currentFrameIndex];
vk::Fence inFlightFence = frame.inFlight.get();
vk::Result fenceResult = this->device.get()->waitForFences(
inFlightFence, vk::True, std::numeric_limits<u64>::max());
if (fenceResult != vk::Result::eSuccess) {
log::error("failed to wait for fence: " + vk::to_string(fenceResult));
}
vk::Semaphore imageAvailable = frame.imageAvailable.get();
vk::Result result = this->swapchain->acquireNextImage(
imageAvailable, this->currentImageIndex);
if (result == vk::Result::eErrorOutOfDateKHR) {
recreateSwapchain();
return nullptr;
}
if (result != vk::Result::eSuccess && result != vk::Result::eSuboptimalKHR) {
log::fatal("failed to acquire swapchain image");
throw std::runtime_error("failed to acquire swapchain image");
}
this->isFrameStarted = true;
vk::Fence& imageInFlight = this->imagesInFlight.at(this->currentImageIndex);
if (imageInFlight) {
vk::Result fenceResult = this->device.get()->waitForFences(
imageInFlight, vk::True, std::numeric_limits<u64>::max());
if (fenceResult != vk::Result::eSuccess) {
log::error("failed to wait for fence: " + vk::to_string(fenceResult));
}
}
imageInFlight = inFlightFence;
this->device.get()->resetFences(inFlightFence);
frame.commandBuffer.reset();
try {
frame.commandBuffer.begin(vk::CommandBufferBeginInfo{});
} catch (const vk::SystemError& err) {
log::fatal("failed to begin recording command buffer");
throw std::runtime_error("failed to begin recording command buffer");
}
return frame.commandBuffer;
}
void RenderContext::endFrame() {
assert(this->isFrameStarted &&
"Can't call endFrame while no frame is in progress");
FrameData& frame = this->frames[this->currentFrameIndex];
frame.commandBuffer.end();
vk::Semaphore waitSemaphore = frame.imageAvailable.get();
vk::Semaphore signalSemaphore =
this->renderFinishedSemaphores.at(this->currentImageIndex).get();
vk::PipelineStageFlags waitStage =
vk::PipelineStageFlagBits::eColorAttachmentOutput;
vk::CommandBuffer commandBuffer = frame.commandBuffer;
vk::SubmitInfo submitInfo{};
submitInfo.waitSemaphoreCount = 1;
submitInfo.pWaitSemaphores = &waitSemaphore;
submitInfo.pWaitDstStageMask = &waitStage;
submitInfo.commandBufferCount = 1;
submitInfo.pCommandBuffers = &commandBuffer;
submitInfo.signalSemaphoreCount = 1;
submitInfo.pSignalSemaphores = &signalSemaphore;
this->device.getGraphicsQueue().submit(submitInfo, frame.inFlight.get());
vk::Result result = this->swapchain->present(
this->device.getPresentQueue(), this->currentImageIndex, signalSemaphore);
if (result == vk::Result::eErrorOutOfDateKHR ||
result == vk::Result::eSuboptimalKHR || this->window.wasResized()) {
this->window.resetResizedFlag();
recreateSwapchain();
} else if (result != vk::Result::eSuccess) {
log::fatal("failed to present swapchain image");
throw std::runtime_error("failed to present swapchain image");
}
this->isFrameStarted = false;
this->currentFrameIndex =
(this->currentFrameIndex + 1) % MAX_FRAMES_IN_FLIGHT;
}
void RenderContext::recreateSwapchain() {
vk::Extent2D extent = this->window.getExtent();
while (extent.width == 0 || extent.height == 0) {
extent = this->window.getExtent();
glfwWaitEvents();
}
this->device.waitIdle();
if (this->swapchain == nullptr) {
this->swapchain = std::make_unique<Swapchain>(this->device, extent);
} else {
std::shared_ptr<Swapchain> oldSwapchain = std::move(this->swapchain);
this->swapchain =
std::make_unique<Swapchain>(this->device, extent, oldSwapchain);
if (!oldSwapchain->compareSwapchainFormats(*this->swapchain)) {
log::fatal("swapchain image format changed on recreation");
throw std::runtime_error("swapchain image format changed on recreation");
}
}
this->renderFinishedSemaphores.clear();
for (size_t i = 0; i < this->swapchain->imageCount(); i++) {
this->renderFinishedSemaphores.push_back(
this->device.get()->createSemaphoreUnique({}));
}
// Old fences referred to images owned by the now-replaced swapchain;
// there's nothing valid left to wait on.
this->imagesInFlight.assign(this->swapchain->imageCount(), vk::Fence{});
log::trace("recreated swapchain");
}
void RenderContext::createFrames() {
vk::CommandBufferAllocateInfo allocInfo{this->device.getCommandPool(),
vk::CommandBufferLevel::ePrimary,
MAX_FRAMES_IN_FLIGHT};
std::vector<vk::CommandBuffer> commandBuffers =
this->device.get()->allocateCommandBuffers(allocInfo);
for (u32 i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) {
this->frames[i].commandBuffer = commandBuffers[i];
this->frames[i].imageAvailable =
this->device.get()->createSemaphoreUnique({});
this->frames[i].inFlight = this->device.get()->createFenceUnique(
{vk::FenceCreateFlagBits::eSignaled});
}
}
} // namespace sophia
+68
View File
@@ -0,0 +1,68 @@
#pragma once
#include <array>
#include <memory>
#include <sophia/types.hpp>
#include <vector>
#include <vulkan/vulkan.hpp>
#include "device.hpp"
#include "swapchain.hpp"
#include "window.hpp"
namespace sophia {
struct FrameData {
vk::CommandBuffer commandBuffer;
vk::UniqueSemaphore imageAvailable;
vk::UniqueFence inFlight;
};
class RenderContext {
public:
static constexpr u32 MAX_FRAMES_IN_FLIGHT = 2;
RenderContext(const RenderContext&) = delete;
RenderContext& operator=(const RenderContext&) = delete;
RenderContext(Window& window, Device& device);
~RenderContext();
vk::CommandBuffer beginFrame();
void endFrame();
void transitionCurrentImage(vk::CommandBuffer commandBuffer,
vk::ImageLayout newLayout) const {
this->swapchain->getImage(static_cast<int>(this->currentImageIndex))
.transitionLayout(commandBuffer, newLayout);
}
vk::ImageView getCurrentImageView() const {
return this->swapchain->getImageView(
static_cast<int>(this->currentImageIndex));
}
vk::Extent2D getExtent() const { return this->swapchain->getExtent(); }
vk::Format getImageFormat() const {
return this->swapchain->getImageFormat();
}
private:
void recreateSwapchain();
void createFrames();
Window& window;
Device& device;
std::unique_ptr<Swapchain> swapchain;
std::array<FrameData, MAX_FRAMES_IN_FLIGHT> frames;
std::vector<vk::UniqueSemaphore> renderFinishedSemaphores;
std::vector<vk::Fence> imagesInFlight;
u32 currentFrameIndex = 0;
u32 currentImageIndex = 0;
bool isFrameStarted = false;
};
} // namespace sophia