Compare commits

...

15 Commits

Author SHA1 Message Date
d435be97e2 Better structure again (whoohoo)! 2026-02-22 17:42:21 +00:00
864c0247bb Made simple render system 2026-02-22 17:27:44 +00:00
8817f90ed1 Rot + Scale! 2026-02-22 15:56:57 +00:00
c7bed7d58e Better pipeline... 2026-02-22 15:43:21 +00:00
8f4792aa2d rebuilt 2026-02-21 17:30:57 +00:00
467910c80d Starting matricies, etc... 2026-02-21 17:30:09 +00:00
13063ad289 Movmenet & duplication! 2026-02-21 15:23:14 +00:00
11783743de Dynamic window resizing 2026-02-20 21:25:33 +00:00
e4124c0aaf fixes + starting on window resizing 2026-02-20 20:35:08 +00:00
253fd3006a Rainbow trianglegit add . 2026-02-20 20:24:42 +00:00
b55769bbf2 Vertex positions no longer hard coded 2026-02-20 19:01:01 +00:00
9477f686c6 cve_model done? 2026-02-20 18:50:50 +00:00
3d211b5da3 more memory stuff 2026-02-20 18:42:03 +00:00
3db30fee6e A load of stuff 2026-02-20 13:38:05 +00:00
7dc47faeab Auto shader compilation 2026-02-20 12:11:06 +00:00
41 changed files with 722 additions and 149 deletions

View File

@@ -1,13 +1,39 @@
CFLAGS = -std=c++17 -O2
CXX ?= g++
CCACHE := $(shell command -v ccache 2>/dev/null)
ifneq ($(CCACHE),)
CXX := ccache $(CXX)
endif
CXXFLAGS = -std=c++17 -O2 -MMD -MP
LDFLAGS = -lglfw -lvulkan -ldl -lpthread -lX11 -lXxf86vm -lXrandr -lXi
VulkanTest: *.cpp *.hpp
g++ $(CFLAGS) -o a.out *.cpp $(LDFLAGS)
SOURCES = $(wildcard *.cpp)
HEADERS = $(wildcard *.hpp)
OBJECTS = $(SOURCES:%.cpp=build/%.o)
DEPS = $(OBJECTS:.o=.d)
SHADERS = shaders/simple_shader.vert shaders/simple_shader.frag
SPV = $(SHADERS:=.spv)
VulkanTest: $(SPV) $(OBJECTS)
$(CXX) $(CXXFLAGS) -o VulkanTest $(OBJECTS) $(LDFLAGS)
build/%.o: %.cpp $(HEADERS) | build
$(CXX) $(CXXFLAGS) -c $< -o $@
shaders/%.spv: shaders/%
/usr/local/bin/glslc $< -o $@
build:
mkdir -p build
.PHONY: test clean
test: a.out
./a.out
test: VulkanTest
./VulkanTest
clean:
rm -f a.out
rm -f VulkanTest
rm -rf build
rm -f $(SPV)
-include $(DEPS)

37
README.md Normal file
View File

@@ -0,0 +1,37 @@
# Most linux distros
## To build the container
`docker build -t vulkan-dev .`
## To run the container
```sh
sudo docker run -it \
--device /dev/dri \
-e DISPLAY=$DISPLAY \
-v /tmp/.X11-unix:/tmp/.X11-unix \
-v "$(pwd)":/workspace \
vulkan-dev
```
## To enable X11 access:
`xhost +local:docker`
# Windows
## To build the container (in powershell)
`docker build -t vulkan-dev .`
## To run it
### Option one with WSLg (recommended)
```sh
docker run -it \
-v "$(pwd)":/workspace \
vulkan-dev
```
### Option two with VcXsrv
Set display in powershell:
`$env:DISPLAY="host.docker.internal:0.0"`
Run container:
```sh
docker run -it `
-e DISPLAY=host.docker.internal:0.0 `
-v ${PWD}:/workspace `
vulkan-dev
```
Not working

BIN
VulkanTest Executable file

Binary file not shown.

BIN
a.out

Binary file not shown.

3
build/cve_device.d Normal file
View File

@@ -0,0 +1,3 @@
build/cve_device.o: cve_device.cpp cve_device.hpp cve_window.hpp
cve_device.hpp:
cve_window.hpp:

BIN
build/cve_device.o Normal file

Binary file not shown.

5
build/cve_model.d Normal file
View File

@@ -0,0 +1,5 @@
build/cve_model.o: cve_model.cpp cve_model.hpp cve_device.hpp \
cve_window.hpp
cve_model.hpp:
cve_device.hpp:
cve_window.hpp:

BIN
build/cve_model.o Normal file

Binary file not shown.

6
build/cve_pipeline.d Normal file
View File

@@ -0,0 +1,6 @@
build/cve_pipeline.o: cve_pipeline.cpp cve_pipeline.hpp cve_device.hpp \
cve_window.hpp cve_model.hpp
cve_pipeline.hpp:
cve_device.hpp:
cve_window.hpp:
cve_model.hpp:

BIN
build/cve_pipeline.o Normal file

Binary file not shown.

6
build/cve_renderer.d Normal file
View File

@@ -0,0 +1,6 @@
build/cve_renderer.o: cve_renderer.cpp cve_renderer.hpp cve_window.hpp \
cve_device.hpp cve_swap_chain.hpp
cve_renderer.hpp:
cve_window.hpp:
cve_device.hpp:
cve_swap_chain.hpp:

BIN
build/cve_renderer.o Normal file

Binary file not shown.

5
build/cve_swap_chain.d Normal file
View File

@@ -0,0 +1,5 @@
build/cve_swap_chain.o: cve_swap_chain.cpp cve_swap_chain.hpp \
cve_device.hpp cve_window.hpp
cve_swap_chain.hpp:
cve_device.hpp:
cve_window.hpp:

BIN
build/cve_swap_chain.o Normal file

Binary file not shown.

2
build/cve_window.d Normal file
View File

@@ -0,0 +1,2 @@
build/cve_window.o: cve_window.cpp cve_window.hpp
cve_window.hpp:

BIN
build/cve_window.o Normal file

Binary file not shown.

12
build/first_app.d Normal file
View File

@@ -0,0 +1,12 @@
build/first_app.o: first_app.cpp first_app.hpp cve_window.hpp \
cve_device.hpp cve_game_object.hpp cve_model.hpp cve_renderer.hpp \
cve_swap_chain.hpp simple_render_system.hpp cve_pipeline.hpp
first_app.hpp:
cve_window.hpp:
cve_device.hpp:
cve_game_object.hpp:
cve_model.hpp:
cve_renderer.hpp:
cve_swap_chain.hpp:
simple_render_system.hpp:
cve_pipeline.hpp:

BIN
build/first_app.o Normal file

Binary file not shown.

9
build/main.d Normal file
View File

@@ -0,0 +1,9 @@
build/main.o: main.cpp first_app.hpp cve_window.hpp cve_device.hpp \
cve_game_object.hpp cve_model.hpp cve_renderer.hpp cve_swap_chain.hpp
first_app.hpp:
cve_window.hpp:
cve_device.hpp:
cve_game_object.hpp:
cve_model.hpp:
cve_renderer.hpp:
cve_swap_chain.hpp:

BIN
build/main.o Normal file

Binary file not shown.

View File

@@ -0,0 +1,9 @@
build/simple_render_system.o: simple_render_system.cpp \
simple_render_system.hpp cve_pipeline.hpp cve_device.hpp cve_window.hpp \
cve_game_object.hpp cve_model.hpp
simple_render_system.hpp:
cve_pipeline.hpp:
cve_device.hpp:
cve_window.hpp:
cve_game_object.hpp:
cve_model.hpp:

Binary file not shown.

48
cve_game_object.hpp Normal file
View File

@@ -0,0 +1,48 @@
#pragma once
#include "cve_model.hpp"
#include <memory>
namespace cve {
struct Transform2dComponent {
glm::vec2 translation{}; // Position offset
glm::vec2 scale{1.f, 1.f};
float rotation;
glm::mat2 mat2() {
const float s = glm::sin(rotation);
const float c = glm::cos(rotation);
glm::mat2 rotMatrix{{c, s}, {-s, c}};
glm::mat2 scaleMat{{scale.x, .0f}, {.0f, scale.y}};
return rotMatrix*scaleMat;
};
};
class CveGameObject {
public:
using id_t = unsigned int;
static CveGameObject createGameObject() {
static id_t currentId = 0;
return CveGameObject{currentId++};
}
CveGameObject(const CveGameObject &) = delete;
CveGameObject &operator=(const CveGameObject &) = delete;
CveGameObject(CveGameObject &&) = default;
CveGameObject &operator=(CveGameObject &&) = default;
const id_t getId() { return id; }
std::shared_ptr<CveModel> model{};
glm::vec3 color;
Transform2dComponent transform2d{};
private:
CveGameObject(id_t objID) : id{objID} {}
id_t id;
};
}

67
cve_model.cpp Normal file
View File

@@ -0,0 +1,67 @@
#include "cve_model.hpp"
#include <cassert>
#include <cstring>
namespace cve {
CveModel::CveModel(CveDevice &device, const std::vector<Vertex> &verticies) : cveDevice{device} {
createVertexBuffers(verticies);
}
CveModel::~CveModel() {
vkDestroyBuffer(cveDevice.device(), vertexBuffer, nullptr);
vkFreeMemory(cveDevice.device(), vertexBufferMemory, nullptr);
}
void CveModel::createVertexBuffers(const std::vector<Vertex> &verticies) {
vertexCount = static_cast<uint32_t>(verticies.size());
assert(vertexCount >= 3 && "Vertex count must be at least 3");
VkDeviceSize bufferSize = sizeof(verticies[0]) * vertexCount;
cveDevice.createBuffer(
bufferSize,
VK_BUFFER_USAGE_VERTEX_BUFFER_BIT,
VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT,
vertexBuffer,
vertexBufferMemory
);
void *data;
vkMapMemory(cveDevice.device(), vertexBufferMemory, 0, bufferSize, 0, &data);
memcpy(data, verticies.data(), static_cast<size_t>(bufferSize));
vkUnmapMemory(cveDevice.device(), vertexBufferMemory);
}
void CveModel::draw(VkCommandBuffer commandBuffer) {
vkCmdDraw(commandBuffer, vertexCount, 1, 0, 0);
}
void CveModel::bind(VkCommandBuffer commandBuffer) {
VkBuffer buffers[] = {vertexBuffer};
VkDeviceSize offsets[] = {0};
vkCmdBindVertexBuffers(commandBuffer, 0, 1, buffers, offsets);
}
std::vector<VkVertexInputBindingDescription> CveModel::Vertex::getBindingDescriptions() {
std::vector<VkVertexInputBindingDescription> bindingDescriptions(1);
bindingDescriptions[0].binding = 0;
bindingDescriptions[0].stride = sizeof(Vertex);
bindingDescriptions[0].inputRate = VK_VERTEX_INPUT_RATE_VERTEX;
return bindingDescriptions;
}
std::vector<VkVertexInputAttributeDescription> CveModel::Vertex::getAttributeDescriptions() {
std::vector<VkVertexInputAttributeDescription> attributeDescriptions(2);
attributeDescriptions[0].binding = 0;
attributeDescriptions[0].location = 0;
attributeDescriptions[0].format = VK_FORMAT_R32G32_SFLOAT;
attributeDescriptions[0].offset = offsetof(Vertex, position);
attributeDescriptions[1].binding = 0;
attributeDescriptions[1].location = 1;
attributeDescriptions[1].format = VK_FORMAT_R32G32B32_SFLOAT;
attributeDescriptions[1].offset = offsetof(Vertex, color);
return attributeDescriptions;
}
}

40
cve_model.hpp Normal file
View File

@@ -0,0 +1,40 @@
#pragma once
#include <vector>
#include "cve_device.hpp"
#define GLM_FORCE_RADIANS
#define GLM_FORCE_DEPTH_ZERO_TO_ONE
#include <glm/glm.hpp>
namespace cve {
class CveModel {
public:
struct Vertex {
glm::vec2 position;
glm::vec3 color;
static std::vector<VkVertexInputBindingDescription> getBindingDescriptions();
static std::vector<VkVertexInputAttributeDescription> getAttributeDescriptions();
};
CveModel(CveDevice &device, const std::vector<Vertex> &verticies);
~CveModel();
CveModel(const CveModel &) = delete;
CveModel &operator=(const CveModel &) = delete;
void bind(VkCommandBuffer commandBuffer);
void draw(VkCommandBuffer commandBuffer);
private:
void createVertexBuffers(const std::vector<Vertex> &verticies);
CveDevice &cveDevice;
VkBuffer vertexBuffer;
VkDeviceMemory vertexBufferMemory;
uint32_t vertexCount;
};
}

View File

@@ -1,5 +1,7 @@
#include "cve_pipeline.hpp"
#include "cve_model.hpp"
#include <fstream>
#include <stdexcept>
#include <iostream>
@@ -63,19 +65,15 @@ namespace cve {
shaderStages[1].pNext = nullptr;
shaderStages[1].pSpecializationInfo = nullptr;
auto bindingDescriptions = CveModel::Vertex::getBindingDescriptions();
auto attributeDescriptions = CveModel::Vertex::getAttributeDescriptions();
VkPipelineVertexInputStateCreateInfo vertexInputInfo{};
vertexInputInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO;
vertexInputInfo.vertexBindingDescriptionCount = 0;
vertexInputInfo.vertexAttributeDescriptionCount = 0;
vertexInputInfo.pVertexBindingDescriptions = nullptr;
vertexInputInfo.pVertexAttributeDescriptions = nullptr;
VkPipelineViewportStateCreateInfo viewportInfo{};
viewportInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO;
viewportInfo.viewportCount = 1;
viewportInfo.pViewports = &configInfo.viewport;
viewportInfo.scissorCount = 1;
viewportInfo.pScissors = &configInfo.scissor;
vertexInputInfo.vertexAttributeDescriptionCount = static_cast<uint32_t>(attributeDescriptions.size());
vertexInputInfo.vertexBindingDescriptionCount = static_cast<uint32_t>(bindingDescriptions.size());
vertexInputInfo.pVertexBindingDescriptions = bindingDescriptions.data();
vertexInputInfo.pVertexAttributeDescriptions = attributeDescriptions.data();
VkGraphicsPipelineCreateInfo pipelineInfo{};
pipelineInfo.sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO;
@@ -83,12 +81,12 @@ namespace cve {
pipelineInfo.pStages = shaderStages;
pipelineInfo.pVertexInputState = &vertexInputInfo;
pipelineInfo.pInputAssemblyState = &configInfo.inputAssemblyInfo;
pipelineInfo.pViewportState = &viewportInfo;
pipelineInfo.pViewportState = &configInfo.viewportInfo;
pipelineInfo.pRasterizationState = &configInfo.rasterizationInfo;
pipelineInfo.pMultisampleState = &configInfo.multisampleInfo;
pipelineInfo.pColorBlendState = &configInfo.colorBlendInfo;
pipelineInfo.pDepthStencilState = &configInfo.depthStencilInfo;
pipelineInfo.pDynamicState = nullptr;
pipelineInfo.pDynamicState = &configInfo.dynamicStateInfo;
pipelineInfo.layout = configInfo.pipelineLayout;
pipelineInfo.renderPass = configInfo.renderPass;
@@ -118,22 +116,16 @@ namespace cve {
vkCmdBindPipeline(commandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, graphicsPipeline);
}
PipelineConfigInfo CvePipeline::defaultPipelineConfigInfo(uint32_t width, uint32_t height) {
PipelineConfigInfo configInfo{};
void CvePipeline::defaultPipelineConfigInfo(PipelineConfigInfo& configInfo) {
configInfo.inputAssemblyInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO;
configInfo.inputAssemblyInfo.topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST;
configInfo.inputAssemblyInfo.primitiveRestartEnable = VK_FALSE;
configInfo.viewport.x = 0.0f;
configInfo.viewport.y = 0.0f;
configInfo.viewport.width = static_cast<float>(width);
configInfo.viewport.height = static_cast<float>(height);
configInfo.viewport.minDepth = 0.0f;
configInfo.viewport.maxDepth = 1.0f;
configInfo.scissor.offset = {0, 0};
configInfo.scissor.extent = {width, height};
configInfo.viewportInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO;
configInfo.viewportInfo.viewportCount = 1;
configInfo.viewportInfo.pViewports = nullptr;
configInfo.viewportInfo.scissorCount = 1;
configInfo.viewportInfo.pScissors = nullptr;
configInfo.rasterizationInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO;
configInfo.rasterizationInfo.depthClampEnable = VK_FALSE;
@@ -187,6 +179,11 @@ namespace cve {
configInfo.depthStencilInfo.front = {}; // Optional
configInfo.depthStencilInfo.back = {}; // Optional
return configInfo;
configInfo.dynamicStateEnables = {VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR};
configInfo.dynamicStateInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO;
configInfo.dynamicStateInfo.pDynamicStates = configInfo.dynamicStateEnables.data();
configInfo.dynamicStateInfo.dynamicStateCount =
static_cast<uint32_t>(configInfo.dynamicStateEnables.size());
configInfo.dynamicStateInfo.flags = 0;
}
}

View File

@@ -7,14 +7,18 @@
namespace cve {
struct PipelineConfigInfo {
VkViewport viewport;
VkRect2D scissor;
PipelineConfigInfo(const PipelineConfigInfo&) = delete;
PipelineConfigInfo& operator=(const PipelineConfigInfo&) = delete;
VkPipelineViewportStateCreateInfo viewportInfo;
VkPipelineInputAssemblyStateCreateInfo inputAssemblyInfo;
VkPipelineRasterizationStateCreateInfo rasterizationInfo;
VkPipelineMultisampleStateCreateInfo multisampleInfo;
VkPipelineColorBlendAttachmentState colorBlendAttachment;
VkPipelineColorBlendStateCreateInfo colorBlendInfo;
VkPipelineDepthStencilStateCreateInfo depthStencilInfo;
std::vector<VkDynamicState> dynamicStateEnables;
VkPipelineDynamicStateCreateInfo dynamicStateInfo;
VkPipelineLayout pipelineLayout = nullptr;
VkRenderPass renderPass = nullptr;
uint32_t subpass = 0;
@@ -26,10 +30,10 @@ namespace cve {
~CvePipeline();
CvePipeline(const CvePipeline&) = delete;
void operator=(const CvePipeline&) = delete;
CvePipeline& operator=(const CvePipeline&) = delete;
void bind(VkCommandBuffer commandBuffer);
static PipelineConfigInfo defaultPipelineConfigInfo(uint32_t width, uint32_t height);
static void defaultPipelineConfigInfo(PipelineConfigInfo& configInfo);
private:
static std::vector<char> readFile(const std::string& filepath);

146
cve_renderer.cpp Normal file
View File

@@ -0,0 +1,146 @@
#include "cve_renderer.hpp"
#include <stdexcept>
#include <array>
namespace cve {
CveRenderer::CveRenderer(CveWindow& window, CveDevice& device) : cveWindow{window}, cveDevice{device}{
recreateSwapChain();
createCommandBuffers();
}
CveRenderer::~CveRenderer() {
freeCommandBuffers();
}
void CveRenderer::recreateSwapChain() {
auto extent = cveWindow.getExtent();
while (extent.width == 0 || extent.height == 0) {
extent = cveWindow.getExtent();
glfwWaitEvents();
}
vkDeviceWaitIdle(cveDevice.device());
if (cveSwapChain == nullptr) {
cveSwapChain = std::make_unique<CveSwapChain>(cveDevice, extent);
} else {
std::shared_ptr<CveSwapChain> oldSwapChain = std::move(cveSwapChain);
cveSwapChain = std::make_unique<CveSwapChain>(cveDevice, extent, oldSwapChain);
if (!oldSwapChain->compareSwapFormats(*cveSwapChain.get())) {
throw std::runtime_error("Swap chain image format/depth has changed.");
}
}
// Come back
}
void CveRenderer::createCommandBuffers() {
commandBuffers.resize(CveSwapChain::MAX_FRAMES_IN_FLIGHT);
VkCommandBufferAllocateInfo allocInfo{};
allocInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO;
allocInfo.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY;
allocInfo.commandPool = cveDevice.getCommandPool();
allocInfo.commandBufferCount = static_cast<uint32_t>(commandBuffers.size());
if (vkAllocateCommandBuffers(cveDevice.device(), &allocInfo, commandBuffers.data()) !=
VK_SUCCESS) {
throw std::runtime_error("Failed to allocate command buffers");
}
}
void CveRenderer::freeCommandBuffers() {
vkFreeCommandBuffers(cveDevice.device(), cveDevice.getCommandPool(),
static_cast<uint32_t>(commandBuffers.size()), commandBuffers.data());
commandBuffers.clear();
}
VkCommandBuffer CveRenderer::beginFrame() {
assert(!isFrameStarted && "Can't call begin frame if frame is already in progress");
auto result = cveSwapChain->acquireNextImage(&currentImageIndex);
if (result == VK_ERROR_OUT_OF_DATE_KHR) {
recreateSwapChain();
return nullptr;
}
if (result != VK_SUCCESS && result != VK_SUBOPTIMAL_KHR) {
throw std::runtime_error("Failed to acquire swap chain image!");
}
isFrameStarted = true;
auto commandBuffer = getCurrentCommandBuffer();
VkCommandBufferBeginInfo beginInfo{};
beginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO;
if (vkBeginCommandBuffer(commandBuffer, &beginInfo) != VK_SUCCESS) {
throw std::runtime_error("Failed to begin command buffer recording");
}
return commandBuffer;
}
void CveRenderer::endFrame() {
assert(isFrameStarted && "Can't end a frame that hasn't started");
auto commandBuffer = getCurrentCommandBuffer();
if (vkEndCommandBuffer(commandBuffer) != VK_SUCCESS) {
throw std::runtime_error("Failed to record command buffer!");
}
auto result = cveSwapChain->submitCommandBuffers(&commandBuffer, &currentImageIndex);
if (result == VK_ERROR_OUT_OF_DATE_KHR || result == VK_SUBOPTIMAL_KHR || cveWindow.wasWindowResized()) {
cveWindow.resetWindowResizedFlag();
recreateSwapChain();
} else if (result != VK_SUCCESS) {
throw std::runtime_error("Failed to present swap chain image!");
}
isFrameStarted = false;
currentFrameIndex = (currentFrameIndex + 1) % CveSwapChain::MAX_FRAMES_IN_FLIGHT;
}
void CveRenderer::beginSwapChainRenderPass(VkCommandBuffer commandBuffer) {
assert(isFrameStarted && "Can't begin swap chain render pass if frame isn't started");
assert(commandBuffer == getCurrentCommandBuffer() &&
"Can't begin render pass on buffer from another frame.");
VkRenderPassBeginInfo renderPassInfo{};
renderPassInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO;
renderPassInfo.renderPass = cveSwapChain->getRenderPass();
renderPassInfo.framebuffer = cveSwapChain->getFrameBuffer(currentImageIndex);
renderPassInfo.renderArea.offset = {0, 0};
renderPassInfo.renderArea.extent = cveSwapChain->getSwapChainExtent();
std::array<VkClearValue, 2> clearValues{};
clearValues[0].color = {0.01f, 0.01f, 0.01f, 1.0f};
clearValues[1].depthStencil = {1.0f, 0};
renderPassInfo.clearValueCount = static_cast<uint32_t>(clearValues.size());
renderPassInfo.pClearValues = clearValues.data();
vkCmdBeginRenderPass(commandBuffer, &renderPassInfo, VK_SUBPASS_CONTENTS_INLINE);
VkViewport viewport{};
viewport.x = 0.0f;
viewport.y = 0.0f;
viewport.width = static_cast<float>(cveSwapChain->getSwapChainExtent().width);
viewport.height = static_cast<float>(cveSwapChain->getSwapChainExtent().height);
viewport.minDepth = 0.0f;
viewport.maxDepth = 1.0f;
VkRect2D scissor{{0, 0}, cveSwapChain->getSwapChainExtent()};
vkCmdSetViewport(commandBuffer, 0, 1, &viewport);
vkCmdSetScissor(commandBuffer, 0, 1, &scissor);
}
void CveRenderer::endSwapChainRenderPass(VkCommandBuffer commandBuffer) {
assert(isFrameStarted && "Can't end swap chain render pass if frame isn't started");
assert(commandBuffer == getCurrentCommandBuffer() &&
"Can't end render pass on buffer from another frame.");
vkCmdEndRenderPass(commandBuffer);
}
}

53
cve_renderer.hpp Normal file
View File

@@ -0,0 +1,53 @@
#pragma once
#include "cve_window.hpp"
#include "cve_device.hpp"
#include "cve_swap_chain.hpp"
#include <cassert>
#include <memory>
#include <vector>
namespace cve {
class CveRenderer {
public:
CveRenderer(CveWindow &window, CveDevice &device);
~CveRenderer();
CveRenderer(const CveRenderer &) = delete;
CveRenderer &operator=(const CveRenderer &) = delete;
VkRenderPass getSwapChainRenderPass() const { return cveSwapChain->getRenderPass(); }
bool isFrameInProgress() const { return isFrameStarted; }
VkCommandBuffer getCurrentCommandBuffer() const {
assert(isFrameStarted && "Cannot get command buffer when frame is not in progress");
return commandBuffers[currentFrameIndex];
}
int getFrameIndex() const {
assert(isFrameStarted && "Cannot get frame index when frame is not started");
return currentFrameIndex;
}
VkCommandBuffer beginFrame();
void endFrame();
void beginSwapChainRenderPass(VkCommandBuffer commandBuffer);
void endSwapChainRenderPass(VkCommandBuffer commandBuffer);
private:
void createCommandBuffers();
void freeCommandBuffers();
void recreateSwapChain();
CveWindow& cveWindow;
CveDevice& cveDevice;
std::unique_ptr<CveSwapChain> cveSwapChain;
std::vector<VkCommandBuffer> commandBuffers;
uint32_t currentImageIndex = 0;
int currentFrameIndex = 0;
bool isFrameStarted = false;
};
}

View File

@@ -13,6 +13,18 @@ namespace cve {
CveSwapChain::CveSwapChain(CveDevice &deviceRef, VkExtent2D extent)
: device{deviceRef}, windowExtent{extent} {
init();
}
CveSwapChain::CveSwapChain(CveDevice &deviceRef, VkExtent2D extent, std::shared_ptr<CveSwapChain> previous)
: device{deviceRef}, windowExtent{extent}, oldSwapChain{previous} {
init();
// Clean up old swap chain
oldSwapChain = nullptr;
}
void CveSwapChain::init() {
createSwapChain();
createImageViews();
createRenderPass();
@@ -162,7 +174,7 @@ void CveSwapChain::createSwapChain() {
createInfo.presentMode = presentMode;
createInfo.clipped = VK_TRUE;
createInfo.oldSwapchain = VK_NULL_HANDLE;
createInfo.oldSwapchain = oldSwapChain ? oldSwapChain->swapChain : VK_NULL_HANDLE;
if (vkCreateSwapchainKHR(device.device(), &createInfo, nullptr, &swapChain) != VK_SUCCESS) {
throw std::runtime_error("failed to create swap chain!");
@@ -289,6 +301,7 @@ void CveSwapChain::createFramebuffers() {
void CveSwapChain::createDepthResources() {
VkFormat depthFormat = findDepthFormat();
swapChainDepthFormat = depthFormat;
VkExtent2D swapChainExtent = getSwapChainExtent();
depthImages.resize(imageCount());
@@ -362,7 +375,7 @@ void CveSwapChain::createSyncObjects() {
VkSurfaceFormatKHR CveSwapChain::chooseSwapSurfaceFormat(
const std::vector<VkSurfaceFormatKHR> &availableFormats) {
for (const auto &availableFormat : availableFormats) {
if (availableFormat.format == VK_FORMAT_B8G8R8A8_UNORM &&
if (availableFormat.format == VK_FORMAT_B8G8R8A8_SRGB &&
availableFormat.colorSpace == VK_COLOR_SPACE_SRGB_NONLINEAR_KHR) {
return availableFormat;
}

View File

@@ -6,6 +6,7 @@
#include <vulkan/vulkan.h>
// std lib headers
#include <memory>
#include <string>
#include <vector>
@@ -16,10 +17,11 @@ class CveSwapChain {
static constexpr int MAX_FRAMES_IN_FLIGHT = 2;
CveSwapChain(CveDevice &deviceRef, VkExtent2D windowExtent);
CveSwapChain(CveDevice &deviceRef, VkExtent2D windowExtent, std::shared_ptr<CveSwapChain> previous);
~CveSwapChain();
CveSwapChain(const CveSwapChain &) = delete;
void operator=(const CveSwapChain &) = delete;
CveSwapChain &operator=(const CveSwapChain &) = delete;
VkFramebuffer getFrameBuffer(int index) { return swapChainFramebuffers[index]; }
VkRenderPass getRenderPass() { return renderPass; }
@@ -38,7 +40,13 @@ class CveSwapChain {
VkResult acquireNextImage(uint32_t *imageIndex);
VkResult submitCommandBuffers(const VkCommandBuffer *buffers, uint32_t *imageIndex);
bool compareSwapFormats(const CveSwapChain& swapChain) const {
return swapChain.swapChainDepthFormat == swapChainDepthFormat &&
swapChain.swapChainImageFormat == swapChainImageFormat;
};
private:
void init();
void createSwapChain();
void createImageViews();
void createDepthResources();
@@ -54,6 +62,7 @@ class CveSwapChain {
VkExtent2D chooseSwapExtent(const VkSurfaceCapabilitiesKHR &capabilities);
VkFormat swapChainImageFormat;
VkFormat swapChainDepthFormat;
VkExtent2D swapChainExtent;
std::vector<VkFramebuffer> swapChainFramebuffers;
@@ -69,6 +78,7 @@ class CveSwapChain {
VkExtent2D windowExtent;
VkSwapchainKHR swapChain;
std::shared_ptr<CveSwapChain> oldSwapChain;
std::vector<VkSemaphore> imageAvailableSemaphores;
std::vector<VkSemaphore> renderFinishedSemaphores;

View File

@@ -16,10 +16,12 @@ namespace cve {
void CveWindow::initWindow() {
glfwInit();
glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API);
glfwWindowHint(GLFW_RESIZABLE, GLFW_FALSE);
glfwWindowHint(GLFW_RESIZABLE, GLFW_TRUE);
window = glfwCreateWindow(width, height,
windowName.c_str(), nullptr, nullptr);
glfwSetWindowUserPointer(window, this);
glfwSetFramebufferSizeCallback(window, framebufferResizedCallback);
}
void CveWindow::createWindowSurface(VkInstance instance, VkSurfaceKHR *surface) {
@@ -27,4 +29,11 @@ namespace cve {
throw std::runtime_error("Failed to create window surface");
}
}
void CveWindow::framebufferResizedCallback(GLFWwindow *window, int width, int height) {
auto cveWindow = reinterpret_cast<CveWindow *>(glfwGetWindowUserPointer(window));
cveWindow->framebufferResized = true;
cveWindow->width = width;
cveWindow->height = height;
}
}

View File

@@ -16,13 +16,17 @@ class CveWindow {
bool shouldClose() { return glfwWindowShouldClose(window); };
VkExtent2D getExtent() { return { static_cast<uint32_t>(width), static_cast<uint32_t>(height) }; };
bool wasWindowResized() { return framebufferResized; };
void resetWindowResizedFlag() { framebufferResized = false; };
void createWindowSurface(VkInstance instance, VkSurfaceKHR *surface);
private:
static void framebufferResizedCallback(GLFWwindow *window, int width, int height);
void initWindow();
const int width;
const int height;
int width;
int height;
bool framebufferResized = false;
std::string windowName;
GLFWwindow *window;

View File

@@ -1,110 +1,53 @@
#include "first_app.hpp"
#include "simple_render_system.hpp"
#include <stdexcept>
#include <array>
#define GLM_FORCE_RADIANS
#define GLM_FORCE_DEPTH_ZERO_TO_ONE
#include <glm/glm.hpp>
#include <glm/gtc/constants.hpp>
namespace cve {
FirstApp::FirstApp() {
createPipelineLayout();
createPipeline();
createCommandBuffers();
loadGameObjects();
}
FirstApp::~FirstApp() {
vkDestroyPipelineLayout(cveDevice.device(), pipelineLayout, nullptr);
}
FirstApp::~FirstApp() {}
void FirstApp::run() {
SimpleRenderSystem simpleRenderSystem{cveDevice, cveRenderer.getSwapChainRenderPass()};
while (!cveWindow.shouldClose()) {
glfwPollEvents();
drawFrame();
if (auto commandBuffer = cveRenderer.beginFrame()) {
cveRenderer.beginSwapChainRenderPass(commandBuffer);
simpleRenderSystem.renderGameObjects(commandBuffer, gameObjects);
cveRenderer.endSwapChainRenderPass(commandBuffer);
cveRenderer.endFrame();
}
}
vkDeviceWaitIdle(cveDevice.device());
}
void FirstApp::createPipelineLayout() {
VkPipelineLayoutCreateInfo pipelineLayoutInfo{};
pipelineLayoutInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO;
pipelineLayoutInfo.setLayoutCount = 0;
pipelineLayoutInfo.pSetLayouts = nullptr;
pipelineLayoutInfo.pushConstantRangeCount = 0;
pipelineLayoutInfo.pPushConstantRanges = nullptr;
if (vkCreatePipelineLayout(cveDevice.device(), &pipelineLayoutInfo, nullptr, &pipelineLayout) !=
VK_SUCCESS) {
throw std::runtime_error("Failed to create pipeline layout");
}
}
void FirstApp::createPipeline() {
auto pipelineConfig = CvePipeline::defaultPipelineConfigInfo(cveSwapChain.width(), cveSwapChain.height());
pipelineConfig.renderPass = cveSwapChain.getRenderPass();
pipelineConfig.pipelineLayout = pipelineLayout;
cvePipeline = std::make_unique<CvePipeline>(
cveDevice,
"shaders/simple_shader.vert.spv",
"shaders/simple_shader.frag.spv",
pipelineConfig
);
}
void FirstApp::loadGameObjects() {
std::vector<CveModel::Vertex> verticies {
{{0.0f, -0.5f}, {1.0f, 0.0f, 0.0f}},
{{0.5f, 0.5f}, {0.0f, 1.0f, 0.0f}},
{{-0.5f, 0.5f}, {0.0f, 0.0f, 1.0f}}
};
void FirstApp::createCommandBuffers() {
commandBuffers.resize(cveSwapChain.imageCount());
auto cveModel = std::make_shared<CveModel>(cveDevice, verticies);
VkCommandBufferAllocateInfo allocInfo{};
allocInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO;
allocInfo.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY;
allocInfo.commandPool = cveDevice.getCommandPool();
allocInfo.commandBufferCount = static_cast<uint32_t>(commandBuffers.size());
auto triangle = CveGameObject::createGameObject();
triangle.model = cveModel;
triangle.color = {0.1f, 0.8f, 0.1f};
triangle.transform2d.translation.x = .2f;
triangle.transform2d.scale = {2.f, .5f};
triangle.transform2d.rotation = .25f * glm::two_pi<float>();
if (vkAllocateCommandBuffers(cveDevice.device(), &allocInfo, commandBuffers.data()) !=
VK_SUCCESS) {
throw std::runtime_error("Failed to allocate command buffers");
}
for (int i=0; i < commandBuffers.size(); i++) {
VkCommandBufferBeginInfo beginInfo{};
beginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO;
if (vkBeginCommandBuffer(commandBuffers[i], &beginInfo) != VK_SUCCESS) {
throw std::runtime_error("Failed to begin command buffer recording");
}
VkRenderPassBeginInfo renderPassInfo{};
renderPassInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO;
renderPassInfo.renderPass = cveSwapChain.getRenderPass();
renderPassInfo.framebuffer = cveSwapChain.getFrameBuffer(i);
renderPassInfo.renderArea.offset = {0, 0};
renderPassInfo.renderArea.extent = cveSwapChain.getSwapChainExtent();
std::array<VkClearValue, 2> clearValues{};
clearValues[0].color = {0.1f, 0.1f, 0.1f, 1.0f};
clearValues[1].depthStencil = {1.0f, 0};
renderPassInfo.clearValueCount = static_cast<uint32_t>(clearValues.size());
renderPassInfo.pClearValues = clearValues.data();
vkCmdBeginRenderPass(commandBuffers[i], &renderPassInfo, VK_SUBPASS_CONTENTS_INLINE);
cvePipeline->bind(commandBuffers[i]);
vkCmdDraw(commandBuffers[i], 3, 1, 0, 0);
vkCmdEndRenderPass(commandBuffers[i]);
if (vkEndCommandBuffer(commandBuffers[i]) != VK_SUCCESS) {
throw std::runtime_error("Failed to record command buffer!");
}
}
}
void FirstApp::drawFrame() {
uint32_t imageIndex;
auto result = cveSwapChain.acquireNextImage(&imageIndex);
if (result != VK_SUCCESS && result != VK_SUBOPTIMAL_KHR) {
throw std::runtime_error("Failed to acquire swap chain image!");
}
result = cveSwapChain.submitCommandBuffers(&commandBuffers[imageIndex], &imageIndex);
if (result != VK_SUCCESS) {
throw std::runtime_error("Failed to present swap chain image!");
}
gameObjects.push_back(std::move(triangle));
}
}

View File

@@ -1,9 +1,9 @@
#pragma once
#include "cve_window.hpp"
#include "cve_pipeline.hpp"
#include "cve_device.hpp"
#include "cve_swap_chain.hpp"
#include "cve_game_object.hpp"
#include "cve_renderer.hpp"
#include <memory>
#include <vector>
@@ -22,16 +22,11 @@ namespace cve {
void run();
private:
void createPipelineLayout();
void createPipeline();
void createCommandBuffers();
void drawFrame();
void loadGameObjects();
CveWindow cveWindow{WIDTH, HEIGHT, "Hello Vulkan!"};
CveDevice cveDevice{cveWindow};
CveSwapChain cveSwapChain{cveDevice, cveWindow.getExtent()};
std::unique_ptr<CvePipeline> cvePipeline;
VkPipelineLayout pipelineLayout;
std::vector<VkCommandBuffer> commandBuffers;
CveRenderer cveRenderer{cveWindow, cveDevice};
std::vector<CveGameObject> gameObjects;
};
}

View File

@@ -1,7 +1,13 @@
#version 450
layout (location = 0) out vec4 outColor;
layout(location = 0) out vec4 outColor;
layout(push_constant) uniform Push {
mat2 transform;
vec2 offset;
vec3 color;
} push;
void main() {
outColor = vec4(1.0, 0.0, 0.0, 1.0);
outColor = vec4(push.color, 1.0);
}

Binary file not shown.

View File

@@ -1,10 +1,14 @@
#version 450
vec2 positions[3] = vec2[] (
vec2(0.0, -0.5),
vec2(0.5, 0.5),
vec2(-0.5, 0.5)
);
layout(location = 0) in vec2 position;
layout(location = 1) in vec3 color;
layout(push_constant) uniform Push {
mat2 transform;
vec2 offset;
vec3 color;
} push;
void main() {
gl_Position = vec4(positions[gl_VertexIndex], 0.0, 1.0);
gl_Position = vec4(push.transform * position + push.offset, 0.0, 1.0);
}

Binary file not shown.

85
simple_render_system.cpp Normal file
View File

@@ -0,0 +1,85 @@
#include "simple_render_system.hpp"
#include <stdexcept>
#include <array>
#define GLM_FORCE_RADIANS
#define GLM_FORCE_DEPTH_ZERO_TO_ONE
#include <glm/glm.hpp>
#include <glm/gtc/constants.hpp>
namespace cve {
struct SimplePushConstantData{
glm::mat2 transform{1.f}; // Identity matrix
glm::vec2 offset;
alignas(16) glm::vec3 color;
};
SimpleRenderSystem::SimpleRenderSystem(CveDevice& device, VkRenderPass renderPass):
cveDevice{device} {
createPipelineLayout();
createPipeline(renderPass);
}
SimpleRenderSystem::~SimpleRenderSystem() {
vkDestroyPipelineLayout(cveDevice.device(), pipelineLayout, nullptr);
}
void SimpleRenderSystem::createPipelineLayout() {
VkPushConstantRange pushConstantRange{};
pushConstantRange.stageFlags = VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_FRAGMENT_BIT;
pushConstantRange.offset = 0;
pushConstantRange.size = sizeof(SimplePushConstantData);
VkPipelineLayoutCreateInfo pipelineLayoutInfo{};
pipelineLayoutInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO;
pipelineLayoutInfo.setLayoutCount = 0;
pipelineLayoutInfo.pSetLayouts = nullptr;
pipelineLayoutInfo.pushConstantRangeCount = 1;
pipelineLayoutInfo.pPushConstantRanges = &pushConstantRange;
if (vkCreatePipelineLayout(cveDevice.device(), &pipelineLayoutInfo, nullptr, &pipelineLayout) !=
VK_SUCCESS) {
throw std::runtime_error("Failed to create pipeline layout");
}
}
void SimpleRenderSystem::createPipeline(VkRenderPass renderPass) {
assert(pipelineLayout != nullptr && "Cannot create pipeline before pipeline layout");
PipelineConfigInfo pipelineConfig{};
CvePipeline::defaultPipelineConfigInfo(pipelineConfig);
pipelineConfig.renderPass = renderPass;
pipelineConfig.pipelineLayout = pipelineLayout;
cvePipeline = std::make_unique<CvePipeline>(
cveDevice,
"shaders/simple_shader.vert.spv",
"shaders/simple_shader.frag.spv",
pipelineConfig
);
}
void SimpleRenderSystem::renderGameObjects(VkCommandBuffer commandBuffer, std::vector<CveGameObject> &gameObjects) {
cvePipeline->bind(commandBuffer);
for (auto& obj: gameObjects) {
obj.transform2d.rotation = glm::mod(obj.transform2d.rotation + 0.001f, glm::two_pi<float>());
SimplePushConstantData push{};
push.offset = obj.transform2d.translation;
push.color = obj.color;
push.transform = obj.transform2d.mat2();
vkCmdPushConstants(
commandBuffer,
pipelineLayout,
VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_FRAGMENT_BIT,
0,
sizeof(SimplePushConstantData),
&push
);
obj.model->bind(commandBuffer);
obj.model->draw(commandBuffer);
}
}
}

29
simple_render_system.hpp Normal file
View File

@@ -0,0 +1,29 @@
#pragma once
#include "cve_pipeline.hpp"
#include "cve_device.hpp"
#include "cve_game_object.hpp"
#include <memory>
#include <vector>
namespace cve {
class SimpleRenderSystem {
public:
SimpleRenderSystem(CveDevice &device, VkRenderPass renderPass);
~SimpleRenderSystem();
SimpleRenderSystem(const SimpleRenderSystem &) = delete;
SimpleRenderSystem &operator=(const SimpleRenderSystem &) = delete;
void renderGameObjects(VkCommandBuffer commandBuffer, std::vector<CveGameObject>& gameObjects);
private:
void createPipelineLayout();
void createPipeline(VkRenderPass renderPass);
CveDevice& cveDevice;
std::unique_ptr<CvePipeline> cvePipeline;
VkPipelineLayout pipelineLayout;
};
}