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));
 	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() {
 graphics::Shader::BuiltinUniformData Graphics::getCurrentBuiltinUniformData() {
 	love::graphics::Shader::BuiltinUniformData data;
 	love::graphics::Shader::BuiltinUniformData data;
 
 
@@ -641,9 +609,9 @@ void Graphics::createVulkanInstance() {
 	VkApplicationInfo appInfo{};
 	VkApplicationInfo appInfo{};
 	appInfo.sType = VK_STRUCTURE_TYPE_APPLICATION_INFO;
 	appInfo.sType = VK_STRUCTURE_TYPE_APPLICATION_INFO;
 	appInfo.pApplicationName = "LOVE";
 	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.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;
 	appInfo.apiVersion = vulkanApiVersion;
 
 
 	VkInstanceCreateInfo createInfo{};
 	VkInstanceCreateInfo createInfo{};
@@ -1283,8 +1251,8 @@ VkSampler Graphics::createSampler(const SamplerState& samplerState) {
 	}
 	}
 	samplerInfo.mipmapMode = Vulkan::getMipMapMode(samplerState.mipmapFilter);
 	samplerInfo.mipmapMode = Vulkan::getMipMapMode(samplerState.mipmapFilter);
 	samplerInfo.mipLodBias = samplerState.lodBias;
 	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;
 	VkSampler sampler;
 	if (vkCreateSampler(device, &samplerInfo, nullptr, &sampler) != VK_SUCCESS) {
 	if (vkCreateSampler(device, &samplerInfo, nullptr, &sampler) != VK_SUCCESS) {
@@ -1505,6 +1473,8 @@ void Graphics::createSyncObjects() {
 void Graphics::createDefaultTexture() {
 void Graphics::createDefaultTexture() {
 	Texture::Settings settings;
 	Texture::Settings settings;
 	standardTexture.reset((Texture*)newTexture(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() {
 void Graphics::cleanup() {

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

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

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

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

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

@@ -11,7 +11,11 @@ namespace love {
 namespace graphics {
 namespace graphics {
 namespace vulkan {
 namespace vulkan {
 Texture::Texture(love::graphics::Graphics* gfx, const Settings& settings, const Slices* data)
 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();
 	loadVolatile();
 }
 }
 
 
@@ -42,8 +46,8 @@ bool Texture::loadVolatile() {
 	imageInfo.extent.width = static_cast<uint32_t>(width);
 	imageInfo.extent.width = static_cast<uint32_t>(width);
 	imageInfo.extent.height = static_cast<uint32_t>(height);
 	imageInfo.extent.height = static_cast<uint32_t>(height);
 	imageInfo.extent.depth = 1;
 	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.format = vulkanFormat.internalFormat;
 	imageInfo.tiling = VK_IMAGE_TILING_OPTIMAL;
 	imageInfo.tiling = VK_IMAGE_TILING_OPTIMAL;
 	imageInfo.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
 	imageInfo.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
@@ -60,32 +64,34 @@ bool Texture::loadVolatile() {
 	auto commandBuffer = vgfx->getDataTransferCommandBuffer();
 	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
 	// 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 {
 	} else {
-		if (isRenderTarget()) {
-			clear(false);
-		}
-		else {
-			clear(true);
-		}
+		clear();
 	}
 	}
 	createTextureImageView();
 	createTextureImageView();
 	textureSampler = vgfx->getCachedSampler(samplerState);
 	textureSampler = vgfx->getCachedSampler(samplerState);
 
 
+	if (slices.getMipmapCount() <= 1 && getMipmapsMode() != MIPMAPS_NONE) {
+		generateMipmaps();
+	}
+
 	return true;
 	return true;
 }
 }
 
 
@@ -138,21 +144,25 @@ void Texture::createTextureImageView() {
 	}
 	}
 }
 }
 
 
-void Texture::clear(bool white) {
+void Texture::clear() {
 	auto commandBuffer = vgfx->getDataTransferCommandBuffer();
 	auto commandBuffer = vgfx->getDataTransferCommandBuffer();
 
 
-	auto clearColor = getClearValue(white);
+	auto clearColor = getClearValue(false);
 
 
 	VkImageSubresourceRange range{};
 	VkImageSubresourceRange range{};
 	range.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
 	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);
 	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) {
 VkClearColorValue Texture::getClearValue(bool white) {
@@ -206,6 +216,83 @@ VkClearColorValue Texture::getClearValue(bool white) {
 	return clearColor;
 	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) {
 void Texture::uploadByteData(PixelFormat pixelformat, const void* data, size_t size, int level, int slice, const Rect& r) {
 	VkBuffer stagingBuffer;
 	VkBuffer stagingBuffer;
 	VmaAllocation vmaAllocation;
 	VmaAllocation vmaAllocation;
@@ -242,7 +329,9 @@ void Texture::uploadByteData(PixelFormat pixelformat, const void* data, size_t s
 
 
 	auto commandBuffer = vgfx->getDataTransferCommandBuffer();
 	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(
 	vkCmdCopyBufferToImage(
 		commandBuffer,
 		commandBuffer,
@@ -253,7 +342,9 @@ void Texture::uploadByteData(PixelFormat pixelformat, const void* data, size_t s
 		&region
 		&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]() {
 	vgfx->queueCleanUp([allocator = allocator, stagingBuffer, vmaAllocation]() {
 		vmaDestroyBuffer(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 bool loadVolatile() override;
 	virtual void unloadVolatile() override;
 	virtual void unloadVolatile() override;
-
+	 
 	void setSamplerState(const SamplerState &s) 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;
 	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 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; };
 	int getMSAA() const override { return 0; };
 	ptrdiff_t getHandle() const override { return (ptrdiff_t)textureImage; }
 	ptrdiff_t getHandle() const override { return (ptrdiff_t)textureImage; }
 
 
 private:
 private:
 	void createTextureImageView();
 	void createTextureImageView();
-	void clear(bool white);
+	void clear();
 
 
 	VkClearColorValue getClearValue(bool white);
 	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;
 	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;
 	int layerCount = 0;
 };
 };
 } // vulkan
 } // vulkan