diff --git a/src/modules/graphics/opengl/Texture.h b/src/modules/graphics/opengl/Texture.h index 8a5163c4b..2806a00bd 100644 --- a/src/modules/graphics/opengl/Texture.h +++ b/src/modules/graphics/opengl/Texture.h @@ -46,8 +46,8 @@ public: bool loadVolatile() override; void unloadVolatile() override; - void copyFromBuffer(love::graphics::Buffer *source, size_t sourceoffset, int sourcewidth, size_t size, int slice, int mipmap, const Rect &rect) override; - void copyToBuffer(love::graphics::Buffer *dest, int slice, int mipmap, const Rect &rect, size_t destoffset, int destwidth, size_t size) override; + void copyFromBuffer(love::graphics::Buffer* source, size_t sourceoffset, int sourcewidth, size_t size, int slice, int mipmap, const Rect& rect) override; + void copyToBuffer(love::graphics::Buffer* dest, int slice, int mipmap, const Rect& rect, size_t destoffset, int destwidth, size_t size) override; void setSamplerState(const SamplerState &s) override; @@ -59,7 +59,6 @@ public: inline GLuint getFBO() const { return fbo; } private: - void createTexture(); void uploadByteData(PixelFormat pixelformat, const void *data, size_t size, int level, int slice, const Rect &r) override; diff --git a/src/modules/graphics/vulkan/Graphics.cpp b/src/modules/graphics/vulkan/Graphics.cpp index 40ffbea0f..ab3487728 100644 --- a/src/modules/graphics/vulkan/Graphics.cpp +++ b/src/modules/graphics/vulkan/Graphics.cpp @@ -94,7 +94,7 @@ namespace love { void Graphics::startRecordingGraphicsCommands() { vkWaitForFences(device, 1, &inFlightFences[currentFrame], VK_TRUE, UINT64_MAX); - + while (true) { VkResult result = vkAcquireNextImageKHR(device, swapChain, UINT64_MAX, imageAvailableSemaphores[currentFrame], VK_NULL_HANDLE, &imageIndex); if (result == VK_ERROR_OUT_OF_DATE_KHR) { @@ -211,7 +211,7 @@ namespace love { recreateSwapChain(); } - bool Graphics::setMode(void* context, int width, int height, int pixelwidth, int pixelheight, bool windowhasstencil, int msaa) { + bool Graphics::setMode(void* context, int width, int height, int pixelwidth, int pixelheight, bool windowhasstencil, int msaa) { std::cout << "setMode "; initCapabilities(); @@ -232,9 +232,6 @@ namespace love { void Graphics::initCapabilities() { std::cout << "initCapabilities "; - - VkPhysicalDeviceProperties properties; - vkGetPhysicalDeviceProperties(physicalDevice, &properties); // todo capabilities.features[FEATURE_MULTI_RENDER_TARGET_FORMATS] = false; @@ -256,6 +253,9 @@ namespace love { capabilities.features[FEATURE_COPY_RENDER_TARGET_TO_BUFFER] = false; static_assert(FEATURE_MAX_ENUM == 17, "Graphics::initCapabilities must be updated when adding a new graphics feature!"); + VkPhysicalDeviceProperties properties; + vkGetPhysicalDeviceProperties(physicalDevice, &properties); + capabilities.limits[LIMIT_POINT_SIZE] = properties.limits.pointSizeRange[1]; capabilities.limits[LIMIT_TEXTURE_SIZE] = properties.limits.maxImageDimension2D; capabilities.limits[LIMIT_TEXTURE_LAYERS] = properties.limits.maxImageArrayLayers; @@ -283,27 +283,59 @@ namespace love { std::cout << "unSetMode "; } - void Graphics::draw(const DrawIndexedCommand& cmd) { + void Graphics::draw(const DrawIndexedCommand& cmd) { std::cout << "drawIndexed "; std::vector buffers; std::vector offsets; buffers.push_back((VkBuffer)cmd.buffers->info[0].buffer->getHandle()); - offsets.push_back((VkDeviceSize) 0); + offsets.push_back((VkDeviceSize)0); vkCmdBindDescriptorSets(commandBuffers.at(imageIndex), VK_PIPELINE_BIND_POINT_GRAPHICS, pipelineLayout, 0, 1, &descriptorSets.at(currentFrame), 0, nullptr); vkCmdBindVertexBuffers(commandBuffers.at(imageIndex), 0, 1, buffers.data(), offsets.data()); - vkCmdBindIndexBuffer(commandBuffers.at(imageIndex), (VkBuffer) cmd.indexBuffer->getHandle(), 0, VK_INDEX_TYPE_UINT16); + vkCmdBindIndexBuffer(commandBuffers.at(imageIndex), (VkBuffer)cmd.indexBuffer->getHandle(), 0, VK_INDEX_TYPE_UINT16); vkCmdDrawIndexed(commandBuffers.at(imageIndex), static_cast(cmd.indexCount), 1, 0, 0, 0); } - graphics::StreamBuffer* Graphics::newStreamBuffer(BufferUsage type, size_t size) { + graphics::StreamBuffer* Graphics::newStreamBuffer(BufferUsage type, size_t size) { std::cout << "newStreamBuffer "; return new StreamBuffer(vmaAllocator, type, size); } // END IMPLEMENTATION OVERRIDDEN FUNCTIONS + VkCommandBuffer Graphics::beginSingleTimeCommands() { + VkCommandBufferAllocateInfo allocInfo{}; + allocInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO; + allocInfo.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY; + allocInfo.commandPool = commandPool; + allocInfo.commandBufferCount = 1; + + VkCommandBuffer commandBuffer; + vkAllocateCommandBuffers(device, &allocInfo, &commandBuffer); + + VkCommandBufferBeginInfo beginInfo{}; + beginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO; + beginInfo.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT; + + vkBeginCommandBuffer(commandBuffer, &beginInfo); + + return commandBuffer; + } + + void Graphics::endSingleTimeCommands(VkCommandBuffer commandBuffer) { + vkEndCommandBuffer(commandBuffer); + + VkSubmitInfo submitInfo{}; + submitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO; + submitInfo.commandBufferCount = 1; + submitInfo.pCommandBuffers = &commandBuffer; + + vkQueueSubmit(graphicsQueue, 1, &submitInfo, VK_NULL_HANDLE); + vkQueueWaitIdle(graphicsQueue); + vkFreeCommandBuffers(device, commandPool, 1, &commandBuffer); + } + void Graphics::prepareDraw(uint32_t currentImage) { auto& buffer = uniformBuffers.at(currentImage); @@ -471,6 +503,10 @@ namespace love { } } + if (!deviceFeatures.samplerAnisotropy) { + score = 0; + } + return score; } @@ -523,6 +559,7 @@ namespace love { } VkPhysicalDeviceFeatures deviceFeatures{}; + deviceFeatures.samplerAnisotropy = VK_TRUE; VkDeviceCreateInfo createInfo{}; createInfo.sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO; diff --git a/src/modules/graphics/vulkan/Graphics.h b/src/modules/graphics/vulkan/Graphics.h index b5d6f0353..b264cfcc1 100644 --- a/src/modules/graphics/vulkan/Graphics.h +++ b/src/modules/graphics/vulkan/Graphics.h @@ -37,8 +37,14 @@ namespace love { return physicalDevice; } + const VmaAllocator getVmaAllocator() const { + return vmaAllocator; + } + // implementation for virtual functions - Texture* newTexture(const Texture::Settings& settings, const Texture::Slices* data = nullptr) override { std::cout << "newTexture "; return new Texture(this, settings, data); } + love::graphics::Texture* newTexture(const love::graphics::Texture::Settings& settings, const love::graphics::Texture::Slices* data = nullptr) override { + return new Texture(this, settings, data); + } love::graphics::Buffer* newBuffer(const love::graphics::Buffer::Settings& settings, const std::vector& format, const void* data, size_t size, size_t arraylength) override; void clear(OptionalColorD color, OptionalInt stencil, OptionalDouble depth) override { std::cout << "clear1 "; } void clear(const std::vector& colors, OptionalInt stencil, OptionalDouble depth) override { std::cout << "clear2 "; } @@ -70,6 +76,9 @@ namespace love { void draw(const DrawIndexedCommand& cmd) override; void drawQuads(int start, int count, const VertexAttributes& attributes, const BufferBindings& buffers, graphics::Texture* texture) override { std::cout << "drawQuads "; } + VkCommandBuffer beginSingleTimeCommands(); + void endSingleTimeCommands(VkCommandBuffer); + protected: graphics::ShaderStage* newShaderStageInternal(ShaderStageType stage, const std::string& cachekey, const std::string& source, bool gles) override { std::cout << "newShaderStageInternal "; diff --git a/src/modules/graphics/vulkan/Texture.cpp b/src/modules/graphics/vulkan/Texture.cpp index 5edae46e8..acc87fc9e 100644 --- a/src/modules/graphics/vulkan/Texture.cpp +++ b/src/modules/graphics/vulkan/Texture.cpp @@ -1,15 +1,219 @@ #include "Texture.h" +#include "Graphics.h" + +// make vulkan::Graphics functions available +#define vgfx ((Graphics*)gfx) namespace love { namespace graphics { namespace vulkan { Texture::Texture(love::graphics::Graphics* gfx, const Settings& settings, const Slices* data) - : love::graphics::Texture(gfx, settings, data) { + : love::graphics::Texture(gfx, settings, data), slices(settings.type), gfx(gfx) { + allocator = vgfx->getVmaAllocator(); + device = vgfx->getDevice(); + + if (data != nullptr) { + slices = *data; + } + loadVolatile(); + } + + Texture::~Texture() { + unloadVolatile(); + } + + bool Texture::loadVolatile() { + auto data = slices.get(0, 0); + auto size = data->getSize(); + auto dataPtr = data->getData(); + Rect rect; + rect.x = 0; + rect.y = 0; + rect.w = data->getWidth(); + rect.h = data->getHeight(); + + uploadByteData(PIXELFORMAT_BGRA8_UNORM, dataPtr, size, 0, 0, rect); + createTextureImageView(); + createTextureSampler(); + + return true; + } + + void Texture::unloadVolatile() { + // vkDestroySampler(device, textureSampler, nullptr); + // vkDestroyImageView(device, textureImageView, nullptr); + // vmaDestroyImage(allocator, textureImage, nullptr); + } + + void Texture::createTextureImageView() { + VkImageViewCreateInfo viewInfo{}; + viewInfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO; + viewInfo.image = textureImage; + viewInfo.viewType = VK_IMAGE_VIEW_TYPE_2D; + viewInfo.format = VK_FORMAT_R8G8B8A8_SRGB; + viewInfo.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + viewInfo.subresourceRange.baseMipLevel = 0; + viewInfo.subresourceRange.levelCount = 1; + viewInfo.subresourceRange.baseArrayLayer = 0; + viewInfo.subresourceRange.layerCount = 1; + + if (vkCreateImageView(device, &viewInfo, nullptr, &textureImageView) != VK_SUCCESS) { + throw love::Exception("could not create texture image view"); + } + } + + void Texture::createTextureSampler() { + auto physicalDevice = vgfx->getPhysicalDevice(); + VkPhysicalDeviceProperties properties{}; + vkGetPhysicalDeviceProperties(physicalDevice, &properties); + + VkSamplerCreateInfo samplerInfo{}; + samplerInfo.sType = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO; + samplerInfo.magFilter = VK_FILTER_LINEAR; + samplerInfo.minFilter = VK_FILTER_LINEAR; + samplerInfo.addressModeU = VK_SAMPLER_ADDRESS_MODE_REPEAT; + samplerInfo.addressModeV = VK_SAMPLER_ADDRESS_MODE_REPEAT; + samplerInfo.addressModeW = VK_SAMPLER_ADDRESS_MODE_REPEAT; + samplerInfo.anisotropyEnable = VK_TRUE; + samplerInfo.maxAnisotropy = properties.limits.maxSamplerAnisotropy; + samplerInfo.borderColor = VK_BORDER_COLOR_INT_OPAQUE_BLACK; + samplerInfo.unnormalizedCoordinates = VK_FALSE; + samplerInfo.compareEnable = VK_FALSE; + samplerInfo.compareOp = VK_COMPARE_OP_ALWAYS; + samplerInfo.mipmapMode = VK_SAMPLER_MIPMAP_MODE_LINEAR; + samplerInfo.mipLodBias = 0.0f; + samplerInfo.minLod = 0.0f; + samplerInfo.maxLod = 0.0f; + + if (vkCreateSampler(device, &samplerInfo, nullptr, &textureSampler) != VK_SUCCESS) { + throw love::Exception("failed to create texture sampler"); + } + } + + void Texture::transitionImageLayout(VkImage image, VkFormat format, VkImageLayout oldLayout, VkImageLayout newLayout) { + auto commandBuffer = vgfx->beginSingleTimeCommands(); + + VkPipelineStageFlags sourceStage; + VkPipelineStageFlags destinationStage; + + VkImageMemoryBarrier barrier{}; + barrier.sType = 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; + + 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 love::Exception("unsupported lay transition"); + } + + vkCmdPipelineBarrier( + commandBuffer, + sourceStage, destinationStage, + 0, + 0, nullptr, + 0, nullptr, + 1, &barrier + ); + + vgfx->endSingleTimeCommands(commandBuffer); + } + + void Texture::copyBufferToImage(VkBuffer buffer, VkImage image, uint32_t width, uint32_t height) { + auto commandBuffer = vgfx->beginSingleTimeCommands(); + + 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, + height, 1 + }; + + vkCmdCopyBufferToImage( + commandBuffer, + buffer, + image, + VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, + 1, + ®ion + ); + + vgfx->endSingleTimeCommands(commandBuffer); } void Texture::uploadByteData(PixelFormat pixelformat, const void* data, size_t size, int level, int slice, const Rect& r) { - std::cout << "Texture::uploadByteData "; + VkBuffer stagingBuffer; + VmaAllocation vmaAllocation; + + VkBufferCreateInfo bufferCreateInfo{}; + bufferCreateInfo.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO; + bufferCreateInfo.size = size; + bufferCreateInfo.usage = VK_BUFFER_USAGE_TRANSFER_SRC_BIT; + + VmaAllocationCreateInfo allocCreateInfo = {}; + allocCreateInfo.usage = VMA_MEMORY_USAGE_AUTO; + allocCreateInfo.flags = VMA_ALLOCATION_CREATE_HOST_ACCESS_SEQUENTIAL_WRITE_BIT | VMA_ALLOCATION_CREATE_MAPPED_BIT; + + VmaAllocationInfo allocInfo; + vmaCreateBuffer(allocator, &bufferCreateInfo, &allocCreateInfo, &stagingBuffer, &vmaAllocation, &allocInfo); + + memcpy(allocInfo.pMappedData, data, size); + + VkImageCreateInfo imageInfo{}; + imageInfo.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO; + imageInfo.imageType = VK_IMAGE_TYPE_2D; + imageInfo.extent.width = static_cast(r.w); + imageInfo.extent.height = static_cast(r.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; + + VmaAllocationCreateInfo imageAllocationCreateInfo{}; + + if (vmaCreateImage(allocator, &imageInfo, &imageAllocationCreateInfo, &textureImage, &textureImageAllocation, nullptr) != VK_SUCCESS) { + throw love::Exception("failed to create image"); + } + + transitionImageLayout(textureImage, VK_FORMAT_R8G8B8A8_SRGB, VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL); + copyBufferToImage(stagingBuffer, textureImage, static_cast(r.w), static_cast(r.h)); + transitionImageLayout(textureImage, VK_FORMAT_R8G8B8A8_SRGB, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL); + + vmaDestroyBuffer(allocator, stagingBuffer, vmaAllocation); } } } -} \ No newline at end of file +} diff --git a/src/modules/graphics/vulkan/Texture.h b/src/modules/graphics/vulkan/Texture.h index a321131de..611b800fd 100644 --- a/src/modules/graphics/vulkan/Texture.h +++ b/src/modules/graphics/vulkan/Texture.h @@ -1,13 +1,25 @@ +#ifndef LOVE_GRAPHICS_VULKAN_TEXTURE_H +#define LOVE_GRAPHICS_VULKAN_TEXTURE_H + #include "graphics/Texture.h" +#include "graphics/Volatile.h" + +#include "vk_mem_alloc.h" + #include namespace love { namespace graphics { namespace vulkan { - class Texture : public graphics::Texture { + class Texture : public graphics::Texture, public graphics::Volatile { public: Texture(love::graphics::Graphics* gfx, const Settings& settings, const Slices* data); + ~Texture(); + + bool loadVolatile() override; + + void unloadVolatile() override; void copyFromBuffer(graphics::Buffer* source, size_t sourceoffset, int sourcewidth, size_t size, int slice, int mipmap, const Rect& rect) override { std::cout << "Texture::copyFromBuffer "; }; void copyToBuffer(graphics::Buffer* dest, int slice, int mipmap, const Rect& rect, size_t destoffset, int destwidth, size_t size) override { std::cout << "Texture::copyToBuffer "; }; @@ -22,7 +34,24 @@ namespace love { int getMSAA() const override { std::cout << "Texture::getMSAA "; return 0; }; ptrdiff_t getHandle() const override { std::cout << "Texture::getHandle "; return (ptrdiff_t)0; } + + private: + void transitionImageLayout(VkImage, VkFormat, VkImageLayout oldLayout, VkImageLayout newLayout); + void copyBufferToImage(VkBuffer, VkImage, uint32_t width, uint32_t height); + void createTextureImageView(); + void createTextureSampler(); + + graphics::Graphics* gfx; + VkDevice device; + Slices slices; + VmaAllocator allocator; + VkImage textureImage; + VmaAllocation textureImageAllocation; + VkImageView textureImageView; + VkSampler textureSampler; }; } } -} \ No newline at end of file +} + +#endif