Browse Source

vulkan: partially implement mipmaps

There is no real effect yet when rendering, however debugging the
application with renderdoc reveals that the mipmap generation is
already correct. There's probably something weird going on with the
sampling. I'll have to look into that next.
niki 3 years ago
parent
commit
fbb64a07af

+ 6 - 36
src/modules/graphics/vulkan/Graphics.cpp

@@ -563,38 +563,6 @@ void Graphics::queueCleanUp(std::function<void()> cleanUp) {
 	cleanUpFunctions.at(currentFrame).push_back(std::move(cleanUp));
 }
 
-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);
-}
-
 graphics::Shader::BuiltinUniformData Graphics::getCurrentBuiltinUniformData() {
 	love::graphics::Shader::BuiltinUniformData data;
 
@@ -641,9 +609,9 @@ void Graphics::createVulkanInstance() {
 	VkApplicationInfo appInfo{};
 	appInfo.sType = VK_STRUCTURE_TYPE_APPLICATION_INFO;
 	appInfo.pApplicationName = "LOVE";
-	appInfo.applicationVersion = VK_MAKE_VERSION(1, 0, 0);	//todo, get this version from somewhere else?
+	appInfo.applicationVersion = VK_MAKE_API_VERSION(0, 1, 0, 0);	//todo, get this version from somewhere else?
 	appInfo.pEngineName = "LOVE Engine";
-	appInfo.engineVersion = VK_MAKE_VERSION(VERSION_MAJOR, VERSION_MINOR, VERSION_REV);
+	appInfo.engineVersion = VK_MAKE_API_VERSION(0, VERSION_MAJOR, VERSION_MINOR, VERSION_REV);
 	appInfo.apiVersion = vulkanApiVersion;
 
 	VkInstanceCreateInfo createInfo{};
@@ -1283,8 +1251,8 @@ VkSampler Graphics::createSampler(const SamplerState& samplerState) {
 	}
 	samplerInfo.mipmapMode = Vulkan::getMipMapMode(samplerState.mipmapFilter);
 	samplerInfo.mipLodBias = samplerState.lodBias;
-	samplerInfo.minLod = 0.0f;	// fixme: samplerState.minLod
-	samplerInfo.maxLod = 0.0f;	// fixme: samplerState.maxLod
+	samplerInfo.minLod = static_cast<float>(samplerState.minLod);
+	samplerInfo.maxLod = static_cast<float>(samplerState.maxLod);
 
 	VkSampler sampler;
 	if (vkCreateSampler(device, &samplerInfo, nullptr, &sampler) != VK_SUCCESS) {
@@ -1505,6 +1473,8 @@ void Graphics::createSyncObjects() {
 void Graphics::createDefaultTexture() {
 	Texture::Settings settings;
 	standardTexture.reset((Texture*)newTexture(settings));
+	uint8_t whitePixels[] = {255, 255, 255, 255};
+	standardTexture->replacePixels(whitePixels, sizeof(whitePixels), 0, 0, { 0, 0, 1, 1 }, false);
 }
 
 void Graphics::cleanup() {

+ 3 - 6
src/modules/graphics/vulkan/Graphics.h

@@ -138,9 +138,6 @@ public:
 	VkCommandBuffer getDataTransferCommandBuffer();
 	void queueCleanUp(std::function<void()> cleanUp);
 
-	VkCommandBuffer beginSingleTimeCommands();
-	void endSingleTimeCommands(VkCommandBuffer);
-
 	uint32_t getNumImagesInFlight() const;
 	const PFN_vkCmdPushDescriptorSetKHR getVkCmdPushDescriptorSetKHRFunctionPointer() const;
 	const VkDeviceSize getMinUniformBufferOffsetAlignment() const;
@@ -222,8 +219,8 @@ private:
 	std::vector<VkSemaphore> renderFinishedSemaphores;
 	std::vector<VkFence> inFlightFences;
 	std::vector<VkFence> imagesInFlight;
-	VkDeviceSize minUniformBufferOffsetAlignment;
-	PFN_vkCmdPushDescriptorSetKHR vkCmdPushDescriptorSet;
+	VkDeviceSize minUniformBufferOffsetAlignment = 0;
+	PFN_vkCmdPushDescriptorSetKHR vkCmdPushDescriptorSet = nullptr;
 	size_t currentFrame = 0;
 	uint32_t imageIndex = 0;
 	bool framebufferResized = false;
@@ -239,7 +236,7 @@ private:
 
 	// render pass variables.
 	VkFormat currentFramebufferOutputFormat = VK_FORMAT_UNDEFINED;
-	Texture* renderTargetTexture;
+	Texture* renderTargetTexture = nullptr;
 	float currentViewportWidth = 0;
 	float currentViewportHeight = 0;
 };

+ 1 - 1
src/modules/graphics/vulkan/Shader.cpp

@@ -174,7 +174,7 @@ void Shader::unloadVolatile() {
 		vkDestroyDescriptorSetLayout(device, descriptorSetLayout, nullptr);
 		vkDestroyPipelineLayout(device, pipelineLayout, nullptr);
 	});
-	for (const auto streamBufferVector : streamBuffers) {
+	for (const auto &streamBufferVector : streamBuffers) {
 		for (const auto streamBuffer : streamBufferVector) {
 			delete streamBuffer;
 		}

+ 122 - 31
src/modules/graphics/vulkan/Texture.cpp

@@ -11,7 +11,11 @@ namespace love {
 namespace graphics {
 namespace vulkan {
 Texture::Texture(love::graphics::Graphics* gfx, const Settings& settings, const Slices* data)
-	: love::graphics::Texture(gfx, settings, data), gfx(gfx), data(data) {
+	: love::graphics::Texture(gfx, settings, data), gfx(gfx), slices(settings.type) {
+	if (data) {
+		slices = *data;
+	}
+
 	loadVolatile();
 }
 
@@ -42,8 +46,8 @@ bool Texture::loadVolatile() {
 	imageInfo.extent.width = static_cast<uint32_t>(width);
 	imageInfo.extent.height = static_cast<uint32_t>(height);
 	imageInfo.extent.depth = 1;
-	imageInfo.mipLevels = 1;
-	imageInfo.arrayLayers = layerCount;
+	imageInfo.mipLevels = static_cast<uint32_t>(getMipmapCount());
+	imageInfo.arrayLayers = static_cast<uint32_t>(layerCount);
 	imageInfo.format = vulkanFormat.internalFormat;
 	imageInfo.tiling = VK_IMAGE_TILING_OPTIMAL;
 	imageInfo.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
@@ -60,32 +64,34 @@ bool Texture::loadVolatile() {
 	auto commandBuffer = vgfx->getDataTransferCommandBuffer();
 
 	// fixme: we probably should select a different default layout when the texture is not readable, instead of VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL
-	Vulkan::cmdTransitionImageLayout(commandBuffer, textureImage, VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, 0, 1, 0, layerCount);
-
-	if (data) {
-		for (int slice = 0; slice < layerCount; slice++) {
-			auto sliceData = data->get(slice, 0);
-			auto size = sliceData->getSize();
-			auto dataPtr = sliceData->getData();
-			Rect rect{};
-			rect.x = 0;
-			rect.y = 0;
-			rect.w = sliceData->getWidth();
-			rect.h = sliceData->getHeight();
-
-			uploadByteData(format, dataPtr, size, 0, slice, rect);
+	Vulkan::cmdTransitionImageLayout(commandBuffer, textureImage, 
+		VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, 
+		0, static_cast<uint32_t>(getMipmapCount()), 
+		0, static_cast<uint32_t>(layerCount));
+
+	bool hasdata = slices.get(0, 0) != nullptr;
+
+	if (hasdata) {
+		for (int mip = 0; mip < layerCount; mip++) {
+			// fixme: deal with compressed images.
+
+			for (int slice = 0; slice < slices.getSliceCount(mip); slice++) {
+				auto* id = slices.get(slice, mip);
+				if (id != nullptr) {
+					uploadImageData(id, mip, slice, 0, 0);
+				}
+			}
 		}
 	} else {
-		if (isRenderTarget()) {
-			clear(false);
-		}
-		else {
-			clear(true);
-		}
+		clear();
 	}
 	createTextureImageView();
 	textureSampler = vgfx->getCachedSampler(samplerState);
 
+	if (slices.getMipmapCount() <= 1 && getMipmapsMode() != MIPMAPS_NONE) {
+		generateMipmaps();
+	}
+
 	return true;
 }
 
@@ -138,21 +144,25 @@ void Texture::createTextureImageView() {
 	}
 }
 
-void Texture::clear(bool white) {
+void Texture::clear() {
 	auto commandBuffer = vgfx->getDataTransferCommandBuffer();
 
-	auto clearColor = getClearValue(white);
+	auto clearColor = getClearValue(false);
 
 	VkImageSubresourceRange range{};
 	range.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
-	range.layerCount = layerCount;
-	range.levelCount = 1;
+	range.layerCount = static_cast<uint32_t>(layerCount);
+	range.levelCount = static_cast<uint32_t>(getMipmapCount());
 
-	Vulkan::cmdTransitionImageLayout(commandBuffer, textureImage, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 0, 1, 0, layerCount);
+	Vulkan::cmdTransitionImageLayout(commandBuffer, textureImage, 
+		VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 
+		0, range.levelCount, 0, range.layerCount);
 
 	vkCmdClearColorImage(commandBuffer, textureImage, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, &clearColor, 1, &range);
 
-	Vulkan::cmdTransitionImageLayout(commandBuffer, textureImage, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, 0, 1, 0, layerCount);
+	Vulkan::cmdTransitionImageLayout(commandBuffer, textureImage, 
+		VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, 
+		0, range.levelCount, 0, range.layerCount);
 }
 
 VkClearColorValue Texture::getClearValue(bool white) {
@@ -206,6 +216,83 @@ VkClearColorValue Texture::getClearValue(bool white) {
 	return clearColor;
 }
 
+void Texture::generateMipmapsInternal() {
+	auto commandBuffer = vgfx->getDataTransferCommandBuffer();
+
+	Vulkan::cmdTransitionImageLayout(commandBuffer, textureImage, 
+		VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 
+		0, static_cast<uint32_t>(getMipmapCount()), 0, static_cast<uint32_t>(layerCount));
+
+	VkImageMemoryBarrier barrier{};
+	barrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER;
+	barrier.image = textureImage;
+	barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
+	barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
+	barrier.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
+	barrier.subresourceRange.baseArrayLayer = 0;
+	barrier.subresourceRange.layerCount = static_cast<uint32_t>(layerCount);
+	barrier.subresourceRange.baseMipLevel = 0;
+	barrier.subresourceRange.levelCount = 1u;
+
+	uint32_t mipLevels = static_cast<uint32_t>(getMipmapCount());
+
+	for (uint32_t i = 1; i < mipLevels; i++) {
+		barrier.subresourceRange.baseMipLevel = i - 1;
+		barrier.oldLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL;
+		barrier.newLayout = VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL;
+		barrier.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
+		barrier.dstAccessMask = VK_ACCESS_TRANSFER_READ_BIT;
+
+		vkCmdPipelineBarrier(commandBuffer, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, 0,
+			0, nullptr,
+			0, nullptr,
+			1, &barrier);
+
+		VkImageBlit blit{};
+		blit.srcOffsets[0] = { 0, 0, 0 };
+		blit.srcOffsets[1] = { getWidth(i - 1), getHeight(i - 1), 1 };
+		blit.srcSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
+		blit.srcSubresource.mipLevel = i - 1;
+		blit.srcSubresource.baseArrayLayer = 0;
+		blit.srcSubresource.layerCount = static_cast<uint32_t>(layerCount);
+
+		blit.dstOffsets[0] = { 0, 0, 0 };
+		blit.dstOffsets[1] = { getWidth(i), getHeight(i), 1 };
+		blit.dstSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
+		blit.dstSubresource.mipLevel = i;
+		blit.dstSubresource.baseArrayLayer = 0;
+		blit.dstSubresource.layerCount = static_cast<uint32_t>(layerCount);
+
+		vkCmdBlitImage(commandBuffer, 
+			textureImage, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL,
+			textureImage, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
+			1, &blit,
+			VK_FILTER_LINEAR);
+
+		barrier.oldLayout = VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL;
+		barrier.newLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
+		barrier.srcAccessMask = VK_ACCESS_TRANSFER_READ_BIT;
+		barrier.dstAccessMask = VK_ACCESS_SHADER_READ_BIT;
+
+		vkCmdPipelineBarrier(commandBuffer, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT, 0,
+			0, nullptr,
+			0, nullptr,
+			1, &barrier);
+	}
+
+	barrier.subresourceRange.baseMipLevel = mipLevels - 1;
+	barrier.oldLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL;
+	barrier.newLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
+	barrier.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
+	barrier.dstAccessMask = VK_ACCESS_SHADER_READ_BIT;
+
+	vkCmdPipelineBarrier(commandBuffer,
+		VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT, 0,
+		0, nullptr,
+		0, nullptr,
+		1, &barrier);
+}
+
 void Texture::uploadByteData(PixelFormat pixelformat, const void* data, size_t size, int level, int slice, const Rect& r) {
 	VkBuffer stagingBuffer;
 	VmaAllocation vmaAllocation;
@@ -242,7 +329,9 @@ void Texture::uploadByteData(PixelFormat pixelformat, const void* data, size_t s
 
 	auto commandBuffer = vgfx->getDataTransferCommandBuffer();
 
-	Vulkan::cmdTransitionImageLayout(commandBuffer, textureImage, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, level, 1, slice, 1);
+	Vulkan::cmdTransitionImageLayout(commandBuffer, textureImage, 
+		VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 
+		level, 1, slice, 1);
 
 	vkCmdCopyBufferToImage(
 		commandBuffer,
@@ -253,7 +342,9 @@ void Texture::uploadByteData(PixelFormat pixelformat, const void* data, size_t s
 		&region
 	);
 
-	Vulkan::cmdTransitionImageLayout(commandBuffer, textureImage, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, level, 1, slice, 1);
+	Vulkan::cmdTransitionImageLayout(commandBuffer, textureImage, 
+		VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, 
+		level, 1, slice, 1);
 
 	vgfx->queueCleanUp([allocator = allocator, stagingBuffer, vmaAllocation]() {
 		vmaDestroyBuffer(allocator, stagingBuffer, vmaAllocation);

+ 10 - 10
src/modules/graphics/vulkan/Texture.h

@@ -19,7 +19,7 @@ public:
 
 	virtual bool loadVolatile() override;
 	virtual void unloadVolatile() override;
-
+	 
 	void setSamplerState(const SamplerState &s) override;
 
 	void copyFromBuffer(graphics::Buffer* source, size_t sourceoffset, int sourcewidth, size_t size, int slice, int mipmap, const Rect& rect) override;
@@ -30,25 +30,25 @@ public:
 
 	void uploadByteData(PixelFormat pixelformat, const void* data, size_t size, int level, int slice, const Rect& r) override;
 
-	void generateMipmapsInternal()  override { };
+	void generateMipmapsInternal()  override;
 
 	int getMSAA() const override { return 0; };
 	ptrdiff_t getHandle() const override { return (ptrdiff_t)textureImage; }
 
 private:
 	void createTextureImageView();
-	void clear(bool white);
+	void clear();
 
 	VkClearColorValue getClearValue(bool white);
 
-	graphics::Graphics* gfx;
-	VkDevice device;
-	VmaAllocator allocator;
+	graphics::Graphics* gfx = nullptr;
+	VkDevice device = VK_NULL_HANDLE;
+	VmaAllocator allocator = VK_NULL_HANDLE;
 	VkImage textureImage = VK_NULL_HANDLE;
-	VmaAllocation textureImageAllocation;
-	VkImageView textureImageView;
-	VkSampler textureSampler;
-	const Slices* data;
+	VmaAllocation textureImageAllocation = VK_NULL_HANDLE;
+	VkImageView textureImageView = VK_NULL_HANDLE;
+	VkSampler textureSampler = VK_NULL_HANDLE;
+	Slices slices;
 	int layerCount = 0;
 };
 } // vulkan