Support textures with vulkan; add FPS limiter.

This commit is contained in:
bailwillharr 2022-11-10 14:12:29 +00:00
parent d4106091cd
commit 7cc09484c1
11 changed files with 229 additions and 65 deletions

View File

@ -6,6 +6,7 @@
#include "resources/shader.hpp" #include "resources/shader.hpp"
#include "resources/mesh.hpp" #include "resources/mesh.hpp"
#include "resources/texture.hpp"
#include <vector> #include <vector>
#include <string> #include <string>
@ -26,7 +27,7 @@ public:
void setTexture(const std::string& name); void setTexture(const std::string& name);
std::shared_ptr<resources::Mesh> m_mesh = nullptr; std::shared_ptr<resources::Mesh> m_mesh = nullptr;
// std::shared_ptr<resources::Texture> m_texture; std::shared_ptr<resources::Texture> m_texture;
glm::vec3 m_color = { 1.0f, 1.0f, 1.0f }; glm::vec3 m_color = { 1.0f, 1.0f, 1.0f };
glm::vec3 m_emission = { 0.0f, 0.0f, 0.0f }; glm::vec3 m_emission = { 0.0f, 0.0f, 0.0f };

View File

@ -48,5 +48,6 @@ namespace engine::gfx {
// handles (incomplete types) // handles (incomplete types)
struct Pipeline; struct Pipeline;
struct Buffer; struct Buffer;
struct Texture;
} }

View File

@ -37,6 +37,9 @@ namespace engine {
gfx::Buffer* createBuffer(gfx::BufferType type, uint64_t size, const void* data); gfx::Buffer* createBuffer(gfx::BufferType type, uint64_t size, const void* data);
void destroyBuffer(const gfx::Buffer* buffer); void destroyBuffer(const gfx::Buffer* buffer);
gfx::Texture* createTexture(const void* imageData, uint32_t w, uint32_t h);
void destroyTexture(const gfx::Texture* texture);
// wait until all the active GPU queues have finished working // wait until all the active GPU queues have finished working
void waitIdle(); void waitIdle();

View File

@ -1,27 +1,21 @@
#pragma once #pragma once
#if 0
#include "engine_api.h" #include "engine_api.h"
#include "resource.hpp" #include "resource.hpp"
#include "gfx_device.hpp"
namespace engine::resources { namespace engine::resources {
class ENGINE_API Texture : public Resource { class ENGINE_API Texture : public Resource {
private:
public: public:
Texture(const std::filesystem::path& resPath); Texture(const std::filesystem::path& resPath);
~Texture() override; ~Texture() override;
void bindTexture() const; private:
gfx::Texture* m_gpuTexture;
static void invalidate();
}; };
} }
#endif

View File

@ -42,7 +42,7 @@ void Renderer::setMesh(const std::string& name)
void Renderer::setTexture(const std::string& name) void Renderer::setTexture(const std::string& name)
{ {
// m_texture = parent.res.get<resources::Texture>(name); m_texture = parent.res.get<resources::Texture>(name);
} }
} }

View File

@ -14,6 +14,9 @@
#include "components/mesh_renderer.hpp" #include "components/mesh_renderer.hpp"
#include "components/camera.hpp" #include "components/camera.hpp"
// To allow the FPS-limiter to put the thread to sleep
#include <thread>
namespace engine { namespace engine {
Application::Application(const char* appName, const char* appVersion) Application::Application(const char* appName, const char* appVersion)
@ -51,6 +54,12 @@ namespace engine {
uint64_t lastTick = m_win->getNanos(); uint64_t lastTick = m_win->getNanos();
constexpr int TICKFREQ = 1; // in hz constexpr int TICKFREQ = 1; // in hz
constexpr int FPS_LIMIT = 240;
constexpr auto FRAMETIME_LIMIT = std::chrono::nanoseconds(1000000000 / FPS_LIMIT);
auto beginFrame = std::chrono::steady_clock::now();
auto endFrame = beginFrame + FRAMETIME_LIMIT;
// single-threaded game loop // single-threaded game loop
while (m_win->isRunning()) { while (m_win->isRunning()) {
@ -79,6 +88,12 @@ namespace engine {
/* poll events */ /* poll events */
m_win->getInputAndEvents(); m_win->getInputAndEvents();
/* fps limiter */
std::this_thread::sleep_until(endFrame);
beginFrame = endFrame;
endFrame = beginFrame + FRAMETIME_LIMIT;
} }
gfxdev->waitIdle(); gfxdev->waitIdle();

View File

@ -65,12 +65,6 @@ namespace engine {
VkImageView view; VkImageView view;
}; };
struct Texture {
VkImage image;
VmaAllocation allocation;
// TODO
};
struct Swapchain { struct Swapchain {
VkSwapchainKHR swapchain = VK_NULL_HANDLE; VkSwapchainKHR swapchain = VK_NULL_HANDLE;
@ -122,6 +116,11 @@ namespace engine {
std::array<VkDescriptorSet, FRAMES_IN_FLIGHT> descriptorSets{}; std::array<VkDescriptorSet, FRAMES_IN_FLIGHT> descriptorSets{};
}; };
struct gfx::Texture {
VkImage image;
VmaAllocation alloc;
};
// enum converters // enum converters
@ -735,6 +734,86 @@ namespace engine {
} }
VkCommandBuffer beginOneTimeCommands(VkDevice device, VkCommandPool commandPool, VkQueue queue)
{
VkResult res;
VkCommandBufferAllocateInfo allocInfo{ VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO };
allocInfo.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY;
allocInfo.commandPool = commandPool;
allocInfo.commandBufferCount = 1;
VkCommandBuffer commandBuffer;
res = vkAllocateCommandBuffers(device, &allocInfo, &commandBuffer);
assert(res == VK_SUCCESS);
VkCommandBufferBeginInfo beginInfo{ VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO };
beginInfo.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT;
res = vkBeginCommandBuffer(commandBuffer, &beginInfo);
assert(res == VK_SUCCESS);
return commandBuffer;
}
static void endOneTimeCommands(VkDevice device, VkCommandPool commandPool, VkCommandBuffer commandBuffer, VkQueue queue)
{
VkResult res;
res = vkEndCommandBuffer(commandBuffer);
assert(res == VK_SUCCESS);
VkSubmitInfo submitInfo{ VK_STRUCTURE_TYPE_SUBMIT_INFO };
submitInfo.commandBufferCount = 1;
submitInfo.pCommandBuffers = &commandBuffer;
res = vkQueueSubmit(queue, 1, &submitInfo, VK_NULL_HANDLE);
assert(res == VK_SUCCESS);
res = vkQueueWaitIdle(queue);
assert(res == VK_SUCCESS);
vkFreeCommandBuffers(device, commandPool, 1, &commandBuffer);
}
static void cmdTransitionImageLayout(VkCommandBuffer commandBuffer, VkImageLayout oldLayout, VkImageLayout newLayout, VkImage image)
{
VkImageMemoryBarrier barrier{ VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER };
barrier.oldLayout = oldLayout;
barrier.newLayout = newLayout;
barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
barrier.image = image;
barrier.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
barrier.subresourceRange.baseMipLevel = 0;
barrier.subresourceRange.levelCount = 1;
barrier.subresourceRange.baseArrayLayer = 0;
barrier.subresourceRange.layerCount = 1;
VkPipelineStageFlags sourceStage;
VkPipelineStageFlags destinationStage;
if (oldLayout == VK_IMAGE_LAYOUT_UNDEFINED && newLayout == VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL) {
barrier.srcAccessMask = 0;
barrier.dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
sourceStage = VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT;
destinationStage = VK_PIPELINE_STAGE_TRANSFER_BIT;
}
else if (oldLayout == VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL && newLayout == VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL) {
barrier.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
barrier.dstAccessMask = VK_ACCESS_SHADER_READ_BIT;
sourceStage = VK_PIPELINE_STAGE_TRANSFER_BIT;
destinationStage = VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT;
}
else {
throw std::invalid_argument("unsupported layout transition!");
}
vkCmdPipelineBarrier(commandBuffer, sourceStage, destinationStage, 0, 0, nullptr, 0, nullptr, 1, &barrier);
}
// class definitions // class definitions
struct GFXDevice::Impl { struct GFXDevice::Impl {
@ -1723,6 +1802,102 @@ namespace engine {
delete buffer; delete buffer;
} }
gfx::Texture* GFXDevice::createTexture(const void* imageData, uint32_t w, uint32_t h)
{
auto out = new gfx::Texture;
VkResult res;
size_t imageSize = w * h * 4;
// first load image into staging buffer
VkBuffer stagingBuffer;
VmaAllocation stagingAllocation;
{
VkBufferCreateInfo stagingBufferInfo{ VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO };
stagingBufferInfo.size = imageSize;
stagingBufferInfo.usage = VK_BUFFER_USAGE_TRANSFER_SRC_BIT;
stagingBufferInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
stagingBufferInfo.flags = 0;
VmaAllocationCreateInfo stagingAllocInfo{};
stagingAllocInfo.usage = VMA_MEMORY_USAGE_AUTO;
stagingAllocInfo.flags = VMA_ALLOCATION_CREATE_HOST_ACCESS_SEQUENTIAL_WRITE_BIT;
stagingAllocInfo.requiredFlags = VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT;
res = vmaCreateBuffer(pimpl->allocator, &stagingBufferInfo, &stagingAllocInfo, &stagingBuffer, &stagingAllocation, nullptr);
assert(res == VK_SUCCESS);
void* dataDest;
res = vmaMapMemory(pimpl->allocator, stagingAllocation, &dataDest);
assert(res == VK_SUCCESS);
memcpy(dataDest, imageData, imageSize);
vmaUnmapMemory(pimpl->allocator, stagingAllocation);
}
// create the image
VkImageCreateInfo imageInfo{ VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO };
imageInfo.imageType = VK_IMAGE_TYPE_2D;
imageInfo.extent.width = w;
imageInfo.extent.height = h;
imageInfo.extent.depth = 1;
imageInfo.mipLevels = 1;
imageInfo.arrayLayers = 1;
imageInfo.format = VK_FORMAT_R8G8B8A8_SRGB;
imageInfo.tiling = VK_IMAGE_TILING_OPTIMAL;
imageInfo.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
imageInfo.usage = VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_SAMPLED_BIT;
imageInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
imageInfo.samples = VK_SAMPLE_COUNT_1_BIT;
imageInfo.flags = 0;
VmaAllocationCreateInfo imageAllocInfo{};
imageAllocInfo.usage = VMA_MEMORY_USAGE_AUTO_PREFER_DEVICE;
res = vmaCreateImage(pimpl->allocator, &imageInfo, &imageAllocInfo, &out->image, &out->alloc, nullptr);
assert(res == VK_SUCCESS);
// transition the image layout
{
VkCommandBuffer commandBuffer = beginOneTimeCommands(pimpl->device, pimpl->commandPool, pimpl->gfxQueue.handle);
// begin cmd buffer
cmdTransitionImageLayout(commandBuffer, VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, out->image);
VkBufferImageCopy region{};
region.bufferOffset = 0;
region.bufferRowLength = 0;
region.bufferImageHeight = 0;
region.imageSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
region.imageSubresource.mipLevel = 0;
region.imageSubresource.baseArrayLayer = 0;
region.imageSubresource.layerCount = 1;
region.imageOffset = { 0, 0, 0 };
region.imageExtent.width = w;
region.imageExtent.height = h;
region.imageExtent.depth = 1;
vkCmdCopyBufferToImage(commandBuffer, stagingBuffer, out->image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, &region);
cmdTransitionImageLayout(commandBuffer, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, out->image);
// end cmd buffer
endOneTimeCommands(pimpl->device, pimpl->commandPool, commandBuffer, pimpl->gfxQueue.handle);
}
// destroy staging buffer
vmaDestroyBuffer(pimpl->allocator, stagingBuffer, stagingAllocation);
return out;
}
void GFXDevice::destroyTexture(const gfx::Texture* texture)
{
vmaDestroyImage(pimpl->allocator, texture->image, texture->alloc);
}
void GFXDevice::waitIdle() void GFXDevice::waitIdle()
{ {
vkDeviceWaitIdle(pimpl->device); vkDeviceWaitIdle(pimpl->device);

View File

@ -1,5 +1,3 @@
#if 0
#include "resources/texture.hpp" #include "resources/texture.hpp"
#define STB_IMAGE_IMPLEMENTATION #define STB_IMAGE_IMPLEMENTATION
@ -11,37 +9,25 @@
namespace engine::resources { namespace engine::resources {
// -1 means invalid / no bind
GLuint Texture::s_binded_texture = -1;
void Texture::invalidate()
{
s_binded_texture = -1;
}
// returns false if unable to open file // returns false if unable to open file
static bool readPNG(const std::string& path, std::vector<uint8_t>& texbuf, int *width, int *height, bool *isRGBA) static bool readPNG(const std::string& path, std::vector<uint8_t>& texbuf, int *width, int *height, bool *isRGBA)
{ {
int x, y, n; int x, y, n;
unsigned char *data = stbi_load(path.c_str(), &x, &y, &n, 0); unsigned char *data = stbi_load(path.c_str(), &x, &y, &n, STBI_rgb_alpha);
if (data == nullptr) { if (data == nullptr) {
return false; return false;
} }
const size_t size = (size_t)x * (size_t)y * (size_t)n; const size_t size = (size_t)x * (size_t)y * 4;
texbuf.resize(size); texbuf.resize(size);
memcpy(texbuf.data(), data, size); memcpy(texbuf.data(), data, size);
*width = x; *width = x;
*height = y; *height = y;
if (n == 4) { *isRGBA = true;
*isRGBA = true;
} else {
*isRGBA = false;
}
stbi_image_free(data); stbi_image_free(data);
@ -94,22 +80,12 @@ Texture::Texture(const std::filesystem::path& resPath) : Resource(resPath, "text
if (!success) { if (!success) {
throw std::runtime_error("Unable to open texture: " + resPath.string()); throw std::runtime_error("Unable to open texture: " + resPath.string());
} }
glGenTextures(1, &m_texture); if (isRGBA == false) {
throw std::runtime_error("Currently, only RGBA textures are supported. Size: " + std::to_string(texbuf.size()));
bindTexture(); // glBindTexture
glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST_MIPMAP_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
if (isRGBA) {
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, texbuf.data());
} else {
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGB, GL_UNSIGNED_BYTE, texbuf.data());
} }
glGenerateMipmap(GL_TEXTURE_2D); m_gpuTexture = gfxdev->createTexture(texbuf.data(), (uint32_t)width, (uint32_t)height);
DEBUG("loaded texture {} width: {} height: {} size: {}", resPath.filename().string(), width, height, texbuf.size()); DEBUG("loaded texture {} width: {} height: {} size: {}", resPath.filename().string(), width, height, texbuf.size());
@ -117,18 +93,7 @@ Texture::Texture(const std::filesystem::path& resPath) : Resource(resPath, "text
Texture::~Texture() Texture::~Texture()
{ {
if (s_binded_texture == m_texture) { gfxdev->destroyTexture(m_gpuTexture);
s_binded_texture = -1;
}
} }
void Texture::bindTexture() const }
{
if (s_binded_texture != m_texture) {
glBindTexture(GL_TEXTURE_2D, m_texture);
s_binded_texture = m_texture;
}
}
}
#endif

View File

@ -291,7 +291,7 @@ namespace engine {
void Window::toggleFullscreen() void Window::toggleFullscreen()
{ {
setFullscreen(!m_fullscreen, true); setFullscreen(!m_fullscreen, false);
} }
bool Window::isFullscreen() const bool Window::isFullscreen() const

View File

@ -16,7 +16,7 @@ void main() {
vec3 lightColor = vec3(1.0, 1.0, 1.0); vec3 lightColor = vec3(1.0, 1.0, 1.0);
vec3 ambientColor = vec3(1.0, 1.0, 1.0); vec3 ambientColor = vec3(1.0, 1.0, 1.0);
float ambientStrength = 0.1; float ambientStrength = 0.1;
vec3 baseColor = vec3(fragColor.x * snoise(fragUV * 10.0), fragColor.yz); vec3 baseColor = vec3(fragColor.x * snoise(fragUV * 10.0), mod(fragUV.x, 1.0), mod(fragUV.y, 1.0));
vec3 emission = vec3(0.0, 0.0, 0.0); vec3 emission = vec3(0.0, 0.0, 0.0);
// code // code

View File

@ -9,6 +9,9 @@
#include "components/camera.hpp" #include "components/camera.hpp"
#include "components/mesh_renderer.hpp" #include "components/mesh_renderer.hpp"
#include "resource_manager.hpp"
#include "resources/texture.hpp"
#include "camera_controller.hpp" #include "camera_controller.hpp"
#include "meshgen.hpp" #include "meshgen.hpp"
@ -16,6 +19,8 @@
#include <log.hpp> #include <log.hpp>
#include <string>
void playGame() void playGame()
{ {
engine::Application app(PROJECT_NAME, PROJECT_VERSION); engine::Application app(PROJECT_NAME, PROJECT_VERSION);
@ -115,8 +120,13 @@ void playGame()
}; };
app.scene()->getChild("cube")->createComponent<Spin>(); app.scene()->getChild("cube")->createComponent<Spin>();
auto sphere = app.scene()->createChild("sphere");
app.scene()->printTree(); sphere->transform.position = { 10.0f, 5.0f, 10.0f };
auto sphereRenderer = sphere->createComponent<engine::components::Renderer>();
sphereRenderer->m_mesh = genSphereMesh(5.0f, 100, false);
sphereRenderer->setTexture("textures/cobble_stone.png");
app.gameLoop(); app.gameLoop();
} }