diff --git a/src/engine/renderer/tr_init.c b/src/engine/renderer/tr_init.c index c9998ce..e49247c 100644 --- a/src/engine/renderer/tr_init.c +++ b/src/engine/renderer/tr_init.c @@ -369,7 +369,24 @@ void RB_TakeScreenshot( int x, int y, int width, int height, char *fileName ) { buffer[15] = height >> 8; buffer[16] = 24; // pixel size - qglReadPixels( x, y, width, height, GL_RGB, GL_UNSIGNED_BYTE, buffer+18 ); + if (r_renderAPI->integer == 0) { + qglReadPixels( x, y, width, height, GL_RGB, GL_UNSIGNED_BYTE, buffer+18 ); + } else { + // VULKAN + byte* buffer2 = (byte*) ri.Hunk_AllocateTempMemory(glConfig.vidWidth*glConfig.vidHeight*4); + vk_read_pixels(buffer2); + + byte* buffer_ptr = buffer + 18; + byte* buffer2_ptr = buffer2; + for (int i = 0; i < glConfig.vidWidth * glConfig.vidHeight; i++) { + buffer_ptr[0] = buffer2_ptr[0]; + buffer_ptr[1] = buffer2_ptr[1]; + buffer_ptr[2] = buffer2_ptr[2]; + buffer_ptr += 3; + buffer2_ptr += 4; + } + ri.Hunk_FreeTempMemory(buffer2); + } // swap rgb to bgr c = 18 + width * height * 3; @@ -395,11 +412,14 @@ RB_TakeScreenshotJPEG ================== */ void RB_TakeScreenshotJPEG( int x, int y, int width, int height, char *fileName ) { - byte *buffer; + byte* buffer = (byte*) ri.Hunk_AllocateTempMemory(glConfig.vidWidth*glConfig.vidHeight*4);; - buffer = (byte*) ri.Hunk_AllocateTempMemory(glConfig.vidWidth*glConfig.vidHeight*4); - - qglReadPixels( x, y, width, height, GL_RGBA, GL_UNSIGNED_BYTE, buffer ); + if (r_renderAPI->integer == 0) { + qglReadPixels( x, y, width, height, GL_RGBA, GL_UNSIGNED_BYTE, buffer ); + } else { + // VULKAN + vk_read_pixels(buffer); + } // gamma correct if ( ( tr.overbrightBits > 0 ) && glConfig.deviceSupportsGamma ) { diff --git a/src/engine/renderer/vk.cpp b/src/engine/renderer/vk.cpp index d4480dc..bc0fe48 100644 --- a/src/engine/renderer/vk.cpp +++ b/src/engine/renderer/vk.cpp @@ -87,9 +87,14 @@ static VkSwapchainKHR create_swapchain(VkPhysicalDevice physical_device, VkDevic // transfer destination usage is required by image clear operations if ((surface_caps.supportedUsageFlags & VK_IMAGE_USAGE_TRANSFER_DST_BIT) == 0) - ri.Error(ERR_FATAL, "Vulkan error: VK_IMAGE_USAGE_TRANSFER_DST_BIT is not supported by the swapchain"); + ri.Error(ERR_FATAL, "create_swapchain: VK_IMAGE_USAGE_TRANSFER_DST_BIT is not supported by the swapchain"); + if ((surface_caps.supportedUsageFlags & VK_IMAGE_USAGE_TRANSFER_SRC_BIT) == 0) + ri.Error(ERR_FATAL, "create_swapchain: VK_IMAGE_USAGE_TRANSFER_SRC_BIT is not supported by the swapchain"); - VkImageUsageFlags image_usage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT; + VkImageUsageFlags image_usage = + VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | + VK_IMAGE_USAGE_TRANSFER_DST_BIT | + VK_IMAGE_USAGE_TRANSFER_SRC_BIT; // determine present mode and swapchain image count uint32_t present_mode_count; @@ -553,8 +558,10 @@ static void init_vulkan_library() { INIT_DEVICE_FUNCTION(vkCmdBindIndexBuffer) INIT_DEVICE_FUNCTION(vkCmdBindPipeline) INIT_DEVICE_FUNCTION(vkCmdBindVertexBuffers) + INIT_DEVICE_FUNCTION(vkCmdBlitImage) INIT_DEVICE_FUNCTION(vkCmdClearAttachments) INIT_DEVICE_FUNCTION(vkCmdCopyBufferToImage) + INIT_DEVICE_FUNCTION(vkCmdCopyImage) INIT_DEVICE_FUNCTION(vkCmdDrawIndexed) INIT_DEVICE_FUNCTION(vkCmdEndRenderPass) INIT_DEVICE_FUNCTION(vkCmdPipelineBarrier) @@ -600,6 +607,7 @@ static void init_vulkan_library() { INIT_DEVICE_FUNCTION(vkGetBufferMemoryRequirements) INIT_DEVICE_FUNCTION(vkGetDeviceQueue) INIT_DEVICE_FUNCTION(vkGetImageMemoryRequirements) + INIT_DEVICE_FUNCTION(vkGetImageSubresourceLayout) INIT_DEVICE_FUNCTION(vkMapMemory) INIT_DEVICE_FUNCTION(vkQueueSubmit) INIT_DEVICE_FUNCTION(vkQueueWaitIdle) @@ -648,8 +656,10 @@ static void deinit_vulkan_library() { vkCmdBindIndexBuffer = nullptr; vkCmdBindPipeline = nullptr; vkCmdBindVertexBuffers = nullptr; + vkCmdBlitImage = nullptr; vkCmdClearAttachments = nullptr; vkCmdCopyBufferToImage = nullptr; + vkCmdCopyImage = nullptr; vkCmdDrawIndexed = nullptr; vkCmdEndRenderPass = nullptr; vkCmdPipelineBarrier = nullptr; @@ -695,6 +705,7 @@ static void deinit_vulkan_library() { vkGetBufferMemoryRequirements = nullptr; vkGetDeviceQueue = nullptr; vkGetImageMemoryRequirements = nullptr; + vkGetImageSubresourceLayout = nullptr; vkMapMemory = nullptr; vkQueueSubmit = nullptr; vkQueueWaitIdle = nullptr; @@ -2162,6 +2173,136 @@ void vk_end_frame() { VK_CHECK(vkQueuePresentKHR(vk.queue, &present_info)); } +void vk_read_pixels(byte* buffer) { + vkDeviceWaitIdle(vk.device); + + // Create image in host visible memory to serve as a destination for framebuffer pixels. + VkImageCreateInfo desc; + desc.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO; + desc.pNext = nullptr; + desc.flags = 0; + desc.imageType = VK_IMAGE_TYPE_2D; + desc.format = VK_FORMAT_R8G8B8A8_UNORM; + desc.extent.width = glConfig.vidWidth; + desc.extent.height = glConfig.vidHeight; + desc.extent.depth = 1; + desc.mipLevels = 1; + desc.arrayLayers = 1; + desc.samples = VK_SAMPLE_COUNT_1_BIT; + desc.tiling = VK_IMAGE_TILING_LINEAR; + desc.usage = VK_IMAGE_USAGE_TRANSFER_DST_BIT; + desc.sharingMode = VK_SHARING_MODE_EXCLUSIVE; + desc.queueFamilyIndexCount = 0; + desc.pQueueFamilyIndices = nullptr; + desc.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; + VkImage image; + VK_CHECK(vkCreateImage(vk.device, &desc, nullptr, &image)); + + VkMemoryRequirements memory_requirements; + vkGetImageMemoryRequirements(vk.device, image, &memory_requirements); + VkMemoryAllocateInfo alloc_info; + alloc_info.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO; + alloc_info.pNext = nullptr; + alloc_info.allocationSize = memory_requirements.size; + alloc_info.memoryTypeIndex = find_memory_type(vk.physical_device, memory_requirements.memoryTypeBits, + VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT); + VkDeviceMemory memory; + VK_CHECK(vkAllocateMemory(vk.device, &alloc_info, nullptr, &memory)); + VK_CHECK(vkBindImageMemory(vk.device, image, memory, 0)); + + record_and_run_commands(vk.command_pool, vk.queue, [&image](VkCommandBuffer command_buffer) { + record_image_layout_transition(command_buffer, vk.swapchain_images[vk.swapchain_image_index], vk.surface_format.format, + VK_ACCESS_MEMORY_READ_BIT, VK_IMAGE_LAYOUT_PRESENT_SRC_KHR, + VK_ACCESS_TRANSFER_READ_BIT, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL); + + record_image_layout_transition(command_buffer, image, VK_FORMAT_R8G8B8A8_UNORM, + 0, VK_IMAGE_LAYOUT_UNDEFINED, + VK_ACCESS_TRANSFER_WRITE_BIT, VK_IMAGE_LAYOUT_GENERAL); + }); + + // Check if we can use vkCmdBlitImage for the given source and destination image formats. + bool blit_enabled = true; + { + VkFormatProperties formatProps; + vkGetPhysicalDeviceFormatProperties(vk.physical_device, vk.surface_format.format, &formatProps); + if ((formatProps.optimalTilingFeatures & VK_FORMAT_FEATURE_BLIT_SRC_BIT) == 0) + blit_enabled = false; + + vkGetPhysicalDeviceFormatProperties(vk.physical_device, VK_FORMAT_R8G8B8A8_UNORM, &formatProps); + if ((formatProps.linearTilingFeatures & VK_FORMAT_FEATURE_BLIT_DST_BIT) == 0) + blit_enabled = false; + } + + if (blit_enabled) { + record_and_run_commands(vk.command_pool, vk.queue, [&image](VkCommandBuffer command_buffer) { + VkImageBlit region; + region.srcSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + region.srcSubresource.mipLevel = 0; + region.srcSubresource.baseArrayLayer = 0; + region.srcSubresource.layerCount = 1; + region.srcOffsets[0] = {0, 0, 0}; + region.srcOffsets[1] = {glConfig.vidWidth, glConfig.vidHeight, 1}; + region.dstSubresource = region.srcSubresource; + region.dstOffsets[0] = region.srcOffsets[0]; + region.dstOffsets[1] = region.srcOffsets[1]; + + vkCmdBlitImage(command_buffer, vk.swapchain_images[vk.swapchain_image_index], VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, + image, VK_IMAGE_LAYOUT_GENERAL, 1, ®ion, VK_FILTER_NEAREST); + }); + } else { + record_and_run_commands(vk.command_pool, vk.queue, [&image](VkCommandBuffer command_buffer) { + VkImageCopy region; + region.srcSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + region.srcSubresource.mipLevel = 0; + region.srcSubresource.baseArrayLayer = 0; + region.srcSubresource.layerCount = 1; + region.srcOffset = {0, 0, 0}; + region.dstSubresource = region.srcSubresource; + region.dstOffset = region.srcOffset; + region.extent = {(uint32_t)glConfig.vidWidth, (uint32_t)glConfig.vidHeight, 1}; + + vkCmdCopyImage(command_buffer, vk.swapchain_images[vk.swapchain_image_index], VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, + image, VK_IMAGE_LAYOUT_GENERAL, 1, ®ion); + }); + } + + // Copy data from destination image to memory buffer. + VkImageSubresource subresource; + subresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + subresource.mipLevel = 0; + subresource.arrayLayer = 0; + VkSubresourceLayout layout; + vkGetImageSubresourceLayout(vk.device, image, &subresource, &layout); + + byte* data; + VK_CHECK(vkMapMemory(vk.device, memory, 0, VK_WHOLE_SIZE, 0, (void**)&data)); + data += layout.offset; + + byte* buffer_ptr = buffer + glConfig.vidWidth * (glConfig.vidHeight - 1) * 4; + for (int i = 0; i < glConfig.vidHeight; i++) { + Com_Memcpy(buffer_ptr, data, glConfig.vidWidth * 4); + buffer_ptr -= glConfig.vidWidth * 4; + data += layout.rowPitch; + } + + if (!blit_enabled) { + auto fmt = vk.surface_format.format; + bool swizzle_components = (fmt == VK_FORMAT_B8G8R8A8_SRGB || fmt == VK_FORMAT_B8G8R8A8_UNORM || fmt == VK_FORMAT_B8G8R8A8_SNORM); + if (swizzle_components) { + buffer_ptr = buffer; + for (int i = 0; i < glConfig.vidWidth * glConfig.vidHeight; i++) { + byte tmp = buffer_ptr[0]; + buffer_ptr[0] = buffer_ptr[2]; + buffer_ptr[2] = tmp; + buffer_ptr += 4; + } + } + } + + vkDestroyImage(vk.device, image, nullptr); + vkFreeMemory(vk.device, memory, nullptr); +} + PFN_vkGetInstanceProcAddr vkGetInstanceProcAddr; PFN_vkCreateInstance vkCreateInstance; @@ -2194,8 +2335,10 @@ PFN_vkCmdBindDescriptorSets vkCmdBindDescriptorSets; PFN_vkCmdBindIndexBuffer vkCmdBindIndexBuffer; PFN_vkCmdBindPipeline vkCmdBindPipeline; PFN_vkCmdBindVertexBuffers vkCmdBindVertexBuffers; +PFN_vkCmdBlitImage vkCmdBlitImage; PFN_vkCmdClearAttachments vkCmdClearAttachments; PFN_vkCmdCopyBufferToImage vkCmdCopyBufferToImage; +PFN_vkCmdCopyImage vkCmdCopyImage; PFN_vkCmdDrawIndexed vkCmdDrawIndexed; PFN_vkCmdEndRenderPass vkCmdEndRenderPass; PFN_vkCmdPipelineBarrier vkCmdPipelineBarrier; @@ -2241,6 +2384,7 @@ PFN_vkFreeMemory vkFreeMemory; PFN_vkGetBufferMemoryRequirements vkGetBufferMemoryRequirements; PFN_vkGetDeviceQueue vkGetDeviceQueue; PFN_vkGetImageMemoryRequirements vkGetImageMemoryRequirements; +PFN_vkGetImageSubresourceLayout vkGetImageSubresourceLayout; PFN_vkMapMemory vkMapMemory; PFN_vkQueueSubmit vkQueueSubmit; PFN_vkQueueWaitIdle vkQueueWaitIdle; diff --git a/src/engine/renderer/vk.h b/src/engine/renderer/vk.h index 7c553a4..fed61c9 100644 --- a/src/engine/renderer/vk.h +++ b/src/engine/renderer/vk.h @@ -86,6 +86,8 @@ void vk_bind_stage_specific_resources(VkPipeline pipeline, bool multitexture, bo void vk_begin_frame(); void vk_end_frame(); +void vk_read_pixels(byte* buffer); + // Vulkan specific structures used by the engine. struct Vk_Instance { bool active = false; @@ -233,8 +235,10 @@ extern PFN_vkCmdBindDescriptorSets vkCmdBindDescriptorSets; extern PFN_vkCmdBindIndexBuffer vkCmdBindIndexBuffer; extern PFN_vkCmdBindPipeline vkCmdBindPipeline; extern PFN_vkCmdBindVertexBuffers vkCmdBindVertexBuffers; +extern PFN_vkCmdBlitImage vkCmdBlitImage; extern PFN_vkCmdClearAttachments vkCmdClearAttachments; extern PFN_vkCmdCopyBufferToImage vkCmdCopyBufferToImage; +extern PFN_vkCmdCopyImage vkCmdCopyImage; extern PFN_vkCmdDrawIndexed vkCmdDrawIndexed; extern PFN_vkCmdEndRenderPass vkCmdEndRenderPass; extern PFN_vkCmdPipelineBarrier vkCmdPipelineBarrier; @@ -280,6 +284,7 @@ extern PFN_vkFreeMemory vkFreeMemory; extern PFN_vkGetBufferMemoryRequirements vkGetBufferMemoryRequirements; extern PFN_vkGetDeviceQueue vkGetDeviceQueue; extern PFN_vkGetImageMemoryRequirements vkGetImageMemoryRequirements; +extern PFN_vkGetImageSubresourceLayout vkGetImageSubresourceLayout; extern PFN_vkMapMemory vkMapMemory; extern PFN_vkQueueSubmit vkQueueSubmit; extern PFN_vkQueueWaitIdle vkQueueWaitIdle;