Browse Source

first draft of vulkan texture impl

niki 3 years ago
parent
commit
4e8304255d

+ 2 - 3
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;

+ 46 - 9
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<VkBuffer> buffers;
 				std::vector<VkDeviceSize> 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<uint32_t>(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;

+ 10 - 1
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<love::graphics::Buffer::DataDeclaration>& 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<OptionalColorD>& 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 "; 

+ 207 - 3
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,
+					&region
+				);
+
+				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<uint32_t>(r.w);
+				imageInfo.extent.height = static_cast<uint32_t>(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<uint32_t>(r.w), static_cast<uint32_t>(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);
 			}
 		}
 	}
-}
+}

+ 31 - 2
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 <iostream>
 
 
 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;
 			};
 		}
 	}
-}
+}
+
+#endif