diff --git a/include/components/mesh_renderer.hpp b/include/components/mesh_renderer.hpp index 3d108bb..a8b86fd 100644 --- a/include/components/mesh_renderer.hpp +++ b/include/components/mesh_renderer.hpp @@ -6,6 +6,7 @@ #include "resources/shader.hpp" #include "resources/mesh.hpp" +#include "resources/texture.hpp" #include #include @@ -26,7 +27,7 @@ public: void setTexture(const std::string& name); std::shared_ptr m_mesh = nullptr; -// std::shared_ptr m_texture; + std::shared_ptr m_texture; glm::vec3 m_color = { 1.0f, 1.0f, 1.0f }; glm::vec3 m_emission = { 0.0f, 0.0f, 0.0f }; diff --git a/include/gfx.hpp b/include/gfx.hpp index 028db74..5b5862d 100644 --- a/include/gfx.hpp +++ b/include/gfx.hpp @@ -48,5 +48,6 @@ namespace engine::gfx { // handles (incomplete types) struct Pipeline; struct Buffer; + struct Texture; } diff --git a/include/gfx_device.hpp b/include/gfx_device.hpp index 61237f3..c53e820 100644 --- a/include/gfx_device.hpp +++ b/include/gfx_device.hpp @@ -37,6 +37,9 @@ namespace engine { gfx::Buffer* createBuffer(gfx::BufferType type, uint64_t size, const void* data); 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 void waitIdle(); diff --git a/include/resources/texture.hpp b/include/resources/texture.hpp index 1c26f41..4b79514 100644 --- a/include/resources/texture.hpp +++ b/include/resources/texture.hpp @@ -1,27 +1,21 @@ #pragma once -#if 0 - #include "engine_api.h" #include "resource.hpp" +#include "gfx_device.hpp" namespace engine::resources { class ENGINE_API Texture : public Resource { -private: - public: Texture(const std::filesystem::path& resPath); ~Texture() override; - void bindTexture() const; - - static void invalidate(); +private: + gfx::Texture* m_gpuTexture; }; -} - -#endif \ No newline at end of file +} \ No newline at end of file diff --git a/src/components/mesh_renderer.cpp b/src/components/mesh_renderer.cpp index 6f7a7b9..02962b2 100644 --- a/src/components/mesh_renderer.cpp +++ b/src/components/mesh_renderer.cpp @@ -42,7 +42,7 @@ void Renderer::setMesh(const std::string& name) void Renderer::setTexture(const std::string& name) { -// m_texture = parent.res.get(name); + m_texture = parent.res.get(name); } } diff --git a/src/engine.cpp b/src/engine.cpp index 1ecc2c9..ba27ddc 100644 --- a/src/engine.cpp +++ b/src/engine.cpp @@ -14,6 +14,9 @@ #include "components/mesh_renderer.hpp" #include "components/camera.hpp" +// To allow the FPS-limiter to put the thread to sleep +#include + namespace engine { Application::Application(const char* appName, const char* appVersion) @@ -51,6 +54,12 @@ namespace engine { uint64_t lastTick = m_win->getNanos(); 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 while (m_win->isRunning()) { @@ -79,6 +88,12 @@ namespace engine { /* poll events */ m_win->getInputAndEvents(); + /* fps limiter */ + std::this_thread::sleep_until(endFrame); + + beginFrame = endFrame; + endFrame = beginFrame + FRAMETIME_LIMIT; + } gfxdev->waitIdle(); diff --git a/src/gfx_device_vulkan.cpp b/src/gfx_device_vulkan.cpp index baf4c2e..8eef31b 100644 --- a/src/gfx_device_vulkan.cpp +++ b/src/gfx_device_vulkan.cpp @@ -65,12 +65,6 @@ namespace engine { VkImageView view; }; - struct Texture { - VkImage image; - VmaAllocation allocation; - // TODO - }; - struct Swapchain { VkSwapchainKHR swapchain = VK_NULL_HANDLE; @@ -122,6 +116,11 @@ namespace engine { std::array descriptorSets{}; }; + struct gfx::Texture { + VkImage image; + VmaAllocation alloc; + }; + // 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 struct GFXDevice::Impl { @@ -1723,6 +1802,102 @@ namespace engine { 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, ®ion); + + 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() { vkDeviceWaitIdle(pimpl->device); diff --git a/src/resources/texture.cpp b/src/resources/texture.cpp index bcf4fa4..2fdf635 100644 --- a/src/resources/texture.cpp +++ b/src/resources/texture.cpp @@ -1,5 +1,3 @@ -#if 0 - #include "resources/texture.hpp" #define STB_IMAGE_IMPLEMENTATION @@ -11,37 +9,25 @@ 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 static bool readPNG(const std::string& path, std::vector& texbuf, int *width, int *height, bool *isRGBA) { 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) { 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); memcpy(texbuf.data(), data, size); *width = x; *height = y; - if (n == 4) { - *isRGBA = true; - } else { - *isRGBA = false; - } + *isRGBA = true; stbi_image_free(data); @@ -94,22 +80,12 @@ Texture::Texture(const std::filesystem::path& resPath) : Resource(resPath, "text if (!success) { throw std::runtime_error("Unable to open texture: " + resPath.string()); } - - glGenTextures(1, &m_texture); - - 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()); + + if (isRGBA == false) { + throw std::runtime_error("Currently, only RGBA textures are supported. Size: " + std::to_string(texbuf.size())); } - 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()); @@ -117,18 +93,7 @@ Texture::Texture(const std::filesystem::path& resPath) : Resource(resPath, "text Texture::~Texture() { - if (s_binded_texture == m_texture) { - s_binded_texture = -1; - } + gfxdev->destroyTexture(m_gpuTexture); } -void Texture::bindTexture() const -{ - if (s_binded_texture != m_texture) { - glBindTexture(GL_TEXTURE_2D, m_texture); - s_binded_texture = m_texture; - } -} - -} -#endif \ No newline at end of file +} \ No newline at end of file diff --git a/src/window.cpp b/src/window.cpp index 1cdb3a2..c28aab2 100644 --- a/src/window.cpp +++ b/src/window.cpp @@ -291,7 +291,7 @@ namespace engine { void Window::toggleFullscreen() { - setFullscreen(!m_fullscreen, true); + setFullscreen(!m_fullscreen, false); } bool Window::isFullscreen() const diff --git a/test/res/shader.frag b/test/res/shader.frag index 6f6671e..d11a185 100644 --- a/test/res/shader.frag +++ b/test/res/shader.frag @@ -16,7 +16,7 @@ void main() { vec3 lightColor = vec3(1.0, 1.0, 1.0); vec3 ambientColor = vec3(1.0, 1.0, 1.0); 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); // code diff --git a/test/src/game.cpp b/test/src/game.cpp index 7ace58f..35c3e24 100644 --- a/test/src/game.cpp +++ b/test/src/game.cpp @@ -9,6 +9,9 @@ #include "components/camera.hpp" #include "components/mesh_renderer.hpp" +#include "resource_manager.hpp" +#include "resources/texture.hpp" + #include "camera_controller.hpp" #include "meshgen.hpp" @@ -16,6 +19,8 @@ #include +#include + void playGame() { engine::Application app(PROJECT_NAME, PROJECT_VERSION); @@ -115,8 +120,13 @@ void playGame() }; app.scene()->getChild("cube")->createComponent(); + auto sphere = app.scene()->createChild("sphere"); - app.scene()->printTree(); + sphere->transform.position = { 10.0f, 5.0f, 10.0f }; + + auto sphereRenderer = sphere->createComponent(); + sphereRenderer->m_mesh = genSphereMesh(5.0f, 100, false); + sphereRenderer->setTexture("textures/cobble_stone.png"); app.gameLoop(); }