diff --git a/VkRenderer/postprocess.cpp b/VkRenderer/postprocess.cpp index 19937ce..5def70a 100644 --- a/VkRenderer/postprocess.cpp +++ b/VkRenderer/postprocess.cpp @@ -10,6 +10,15 @@ void PostProcessor::init(VulkanEngine *engine) { _compositorData.useRayTracer = 0; _compositorData.exposure = 4.0f; _compositorData.showGrid = 0; + _compositorData.useFXAA = 0; // TODO: Keeping this on by default doesnt load because of rt accell structure + // validation error. Should debug this later. + + // init FXAA data + _fxaaData.R_inverseFilterTextureSize = + glm::vec3(1.0f / engine->_windowExtent.width, 1.0f / engine->_windowExtent.height, 0.0f); + _fxaaData.R_fxaaSpanMax = 8.0f; + _fxaaData.R_fxaaReduceMin = 1.0f / 128.0f; + _fxaaData.R_fxaaReduceMul = 1.0f / 8.0f; _fullscreenImage = vkutil::create_image( engine, VkExtent3D{engine->_windowExtent.width, engine->_windowExtent.height, 1}, VK_FORMAT_R32G32B32A32_SFLOAT, @@ -17,6 +26,12 @@ void PostProcessor::init(VulkanEngine *engine) { VK_IMAGE_USAGE_SAMPLED_BIT, false, "Post Process Image"); + _fxaaImage = vkutil::create_image(engine, VkExtent3D{engine->_windowExtent.width, engine->_windowExtent.height, 1}, + VK_FORMAT_R8G8B8A8_UNORM, + VK_IMAGE_USAGE_TRANSFER_SRC_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT | + VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | VK_IMAGE_USAGE_SAMPLED_BIT, + false, "FXAA Image"); + // Create a sampler for the image VkSamplerCreateInfo samplerInfo{}; samplerInfo.sType = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO; @@ -88,6 +103,62 @@ void PostProcessor::init(VulkanEngine *engine) { vkDestroyShaderModule(engine->_device, fullscreenDrawFragShader, nullptr); vkDestroyShaderModule(engine->_device, fullscreenDrawVertShader, nullptr); + // FXAA PIPELINE + { + DescriptorLayoutBuilder fxaaBuilder; + fxaaBuilder.add_binding(0, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER); + fxaaBuilder.add_binding(1, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER); + _fxaaDescriptorSetLayout = fxaaBuilder.build(engine->_device, VK_SHADER_STAGE_FRAGMENT_BIT); + } + + // allocate a descriptor set for our FXAA input + _fxaaDescriptorSet = engine->globalDescriptorAllocator.allocate(engine->_device, _fxaaDescriptorSetLayout); + + engine->_mainDeletionQueue.push_function( + [=] { vkDestroyDescriptorSetLayout(engine->_device, _fxaaDescriptorSetLayout, nullptr); }); + + VkPipelineLayoutCreateInfo fxaa_layout_info{}; + fxaa_layout_info.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO; + fxaa_layout_info.pNext = nullptr; + fxaa_layout_info.setLayoutCount = 1; + fxaa_layout_info.pSetLayouts = &_fxaaDescriptorSetLayout; + + VK_CHECK(vkCreatePipelineLayout(engine->_device, &fxaa_layout_info, nullptr, &_fxaaPipelineLayout)); + + engine->_mainDeletionQueue.push_function( + [=] { vkDestroyPipelineLayout(engine->_device, _fxaaPipelineLayout, nullptr); }); + + // FXAA shaders + VkShaderModule fxaaVertShader; + if (!vkutil::load_shader_module("Fullscreen.vert.spv", engine->_device, &fxaaVertShader)) { + spdlog::error("Error when building the FXAA Vertex shader"); + } + VkShaderModule fxaaFragShader; + if (!vkutil::load_shader_module("FXAA.frag.spv", engine->_device, &fxaaFragShader)) { + spdlog::error("Error when building the FXAA Fragment shader"); + } + + // Build FXAA pipeline + PipelineBuilder fxaaPipelineBuilder; + fxaaPipelineBuilder.set_shaders(fxaaVertShader, fxaaFragShader); + fxaaPipelineBuilder.set_input_topology(VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST); + fxaaPipelineBuilder.set_polygon_mode(VK_POLYGON_MODE_FILL); + fxaaPipelineBuilder.set_cull_mode(VK_CULL_MODE_NONE, VK_FRONT_FACE_CLOCKWISE); + fxaaPipelineBuilder.set_multisampling_none(); + fxaaPipelineBuilder.enable_depthtest(false, VK_COMPARE_OP_GREATER_OR_EQUAL); + + // render format + fxaaPipelineBuilder.add_color_attachment(_fxaaImage.imageFormat, PipelineBuilder::BlendMode::NO_BLEND); + + // use the FXAA layout we created + fxaaPipelineBuilder._pipelineLayout = _fxaaPipelineLayout; + + // create the pipeline + _fxaaPipeline = fxaaPipelineBuilder.build_pipeline(engine->_device); + + vkDestroyShaderModule(engine->_device, fxaaFragShader, nullptr); + vkDestroyShaderModule(engine->_device, fxaaVertShader, nullptr); + // GRID PIPELINE { DescriptorLayoutBuilder gridBuilder; @@ -140,9 +211,11 @@ void PostProcessor::init(VulkanEngine *engine) { engine->_mainDeletionQueue.push_function([=] { vkDestroyPipeline(engine->_device, _postProcessPipeline, nullptr); + vkDestroyPipeline(engine->_device, _fxaaPipeline, nullptr); vkDestroyPipeline(engine->_device, _gridPipeline, nullptr); vkDestroySampler(engine->_device, _fullscreenImageSampler, nullptr); vkutil::destroy_image(engine, _fullscreenImage); + vkutil::destroy_image(engine, _fxaaImage); }); } @@ -220,6 +293,74 @@ void PostProcessor::draw(VulkanEngine *engine, VkCommandBuffer cmd) { vkCmdEndRendering(cmd); } +void PostProcessor::draw_fxaa(VulkanEngine *engine, VkCommandBuffer cmd) { + VkClearValue clearVal = {.color = {0.0f, 0.0f, 0.0f, 1.0f}}; + + std::array colorAttachments = { + vkinit::attachment_info(_fxaaImage.imageView, &clearVal, VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL), + }; + + // Pass the color attachments directly to rendering_info + VkRenderingInfo renderInfo = vkinit::rendering_info(engine->_windowExtent, + colorAttachments.data(), // Pass your color attachments directly + nullptr // No depth attachment + ); + + vkCmdBeginRendering(cmd, &renderInfo); + + _fxaaDescriptorSet = + engine->get_current_frame()._frameDescriptors.allocate(engine->_device, _fxaaDescriptorSetLayout); + + // Allocate a new uniform buffer for the FXAA data + AllocatedBuffer fxaaDataBuffer = vkutil::create_buffer(engine, sizeof(FXAAData), VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT, + VMA_MEMORY_USAGE_CPU_TO_GPU, "FXAA Data Buffer"); + + // Add it to the deletion queue + engine->get_current_frame()._deletionQueue.push_function([=] { vkutil::destroy_buffer(engine, fxaaDataBuffer); }); + + // Write the FXAA data + FXAAData *fxaaUniformBuffer; + VK_CHECK( + vmaMapMemory(engine->_allocator, fxaaDataBuffer.allocation, reinterpret_cast(&fxaaUniformBuffer))); + *fxaaUniformBuffer = _fxaaData; + vmaUnmapMemory(engine->_allocator, fxaaDataBuffer.allocation); + + { + DescriptorWriter writer; + writer.write_image(0, _fullscreenImage.imageView, _fullscreenImageSampler, + VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER); + writer.write_buffer(1, fxaaDataBuffer.buffer, sizeof(FXAAData), 0, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER); + writer.update_set(engine->_device, _fxaaDescriptorSet); + } + + // Bind pipeline and descriptors + vkCmdBindPipeline(cmd, VK_PIPELINE_BIND_POINT_GRAPHICS, _fxaaPipeline); + vkCmdBindDescriptorSets(cmd, VK_PIPELINE_BIND_POINT_GRAPHICS, _fxaaPipelineLayout, 0, 1, &_fxaaDescriptorSet, 0, + nullptr); + + // Set viewport and scissor + VkViewport viewport = {}; + viewport.x = 0; + viewport.y = 0; + viewport.width = static_cast(engine->_windowExtent.width); + viewport.height = static_cast(engine->_windowExtent.height); + viewport.minDepth = 0.f; + viewport.maxDepth = 1.f; + + vkCmdSetViewport(cmd, 0, 1, &viewport); + + VkRect2D scissor = {}; + scissor.offset.x = 0; + scissor.offset.y = 0; + scissor.extent = engine->_windowExtent; + + vkCmdSetScissor(cmd, 0, 1, &scissor); + + vkCmdDraw(cmd, 3, 1, 0, 0); // 1 triangle, 3 vertices + + vkCmdEndRendering(cmd); +} + void PostProcessor::draw_grid_only(VulkanEngine *engine, VkCommandBuffer cmd) { // Only draw grid, not the fullscreen composition if (!_compositorData.showGrid) { diff --git a/VkRenderer/postprocess.h b/VkRenderer/postprocess.h index 2b3678f..87a9732 100644 --- a/VkRenderer/postprocess.h +++ b/VkRenderer/postprocess.h @@ -1,5 +1,6 @@ #pragma once +#include #include #include #include "vk_mem_alloc.h" @@ -8,6 +9,14 @@ struct CompositorData { int useRayTracer; float exposure; int showGrid; + int useFXAA; +}; + +struct FXAAData { + alignas(16) glm::vec3 R_inverseFilterTextureSize; + float R_fxaaSpanMax; + float R_fxaaReduceMin; + float R_fxaaReduceMul; }; class VulkanEngine; @@ -16,16 +25,27 @@ class PostProcessor { public: void init(VulkanEngine *engine); void draw(VulkanEngine *engine, VkCommandBuffer cmd); + void draw_fxaa(VulkanEngine *engine, VkCommandBuffer cmd); void draw_grid_only(VulkanEngine *engine, VkCommandBuffer cmd); void draw_grid_geometry(VulkanEngine *engine, VkCommandBuffer cmd); // Fullscreen resources AllocatedImage _fullscreenImage{}; + AllocatedImage _fxaaImage{}; CompositorData _compositorData{}; + FXAAData _fxaaData{}; // Getter for the sampler VkSampler getFullscreenImageSampler() const { return _fullscreenImageSampler; } + // Get the final processed image (FXAA output if enabled, otherwise fullscreen image) + const AllocatedImage &getFinalImage() const { return _compositorData.useFXAA ? _fxaaImage : _fullscreenImage; } + + // Get the final image view for UI display + VkImageView getFinalImageView() const { + return _compositorData.useFXAA ? _fxaaImage.imageView : _fullscreenImage.imageView; + } + private: VkPipelineLayout _postProcessPipelineLayout = nullptr; VkPipeline _postProcessPipeline = nullptr; @@ -34,6 +54,12 @@ class PostProcessor { VkDescriptorSetLayout _postProcessDescriptorSetLayout = nullptr; VkSampler _fullscreenImageSampler = nullptr; + // FXAA pipeline + VkPipelineLayout _fxaaPipelineLayout = nullptr; + VkPipeline _fxaaPipeline = nullptr; + VkDescriptorSet _fxaaDescriptorSet = nullptr; + VkDescriptorSetLayout _fxaaDescriptorSetLayout = nullptr; + VkPipelineLayout _gridPipelineLayout = nullptr; VkPipeline _gridPipeline = nullptr; VkDescriptorSetLayout _gridDescriptorSetLayout = nullptr; diff --git a/VkRenderer/ui.cpp b/VkRenderer/ui.cpp index 9b07f20..b799441 100644 --- a/VkRenderer/ui.cpp +++ b/VkRenderer/ui.cpp @@ -176,6 +176,11 @@ void ui::init_imgui(VulkanEngine *engine) { engine->postProcessor.getFullscreenImageSampler(), engine->postProcessor._fullscreenImage.imageView, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL); + // For Drawing FXAA output in viewport + engine->_fxaaViewportTextureDescriptorSet = ImGui_ImplVulkan_AddTexture( + engine->postProcessor.getFullscreenImageSampler(), engine->postProcessor._fxaaImage.imageView, + VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL); + // add the destroy the imgui created structures engine->_mainDeletionQueue.push_function([=] { ImGui_ImplVulkan_Shutdown(); @@ -315,6 +320,7 @@ void ui::create_settings_panel(VulkanEngine *engine) { if (ImGui::CollapsingHeader("Compositor Settings")) { ImGui::SliderFloat("Exposure", &engine->postProcessor._compositorData.exposure, 0.1f, 10.0f); ImGui::Checkbox("Show Grid Helper", reinterpret_cast(&engine->postProcessor._compositorData.showGrid)); + ImGui::Checkbox("FXAA", reinterpret_cast(&engine->postProcessor._compositorData.useFXAA)); } if (ImGui::CollapsingHeader("Lighting Settings")) { @@ -474,7 +480,10 @@ void ui::create_viewport_panel(VulkanEngine *engine) { } // Display the rendered scene texture with proper scaling - ImGui::Image(reinterpret_cast(engine->_viewportTextureDescriptorSet), displaySize); + VkDescriptorSet textureToDisplay = engine->postProcessor._compositorData.useFXAA + ? engine->_fxaaViewportTextureDescriptorSet + : engine->_viewportTextureDescriptorSet; + ImGui::Image(reinterpret_cast(textureToDisplay), displaySize); // Display info below the image ImGui::Text("Render: %dx%d (%.1fx scale)", engine->_drawExtent.width, engine->_drawExtent.height, diff --git a/VkRenderer/vk_engine.cpp b/VkRenderer/vk_engine.cpp index 8ba1c77..7b366ed 100644 --- a/VkRenderer/vk_engine.cpp +++ b/VkRenderer/vk_engine.cpp @@ -419,8 +419,23 @@ void VulkanEngine::draw() { postProcessor.draw(this, cmd); - vkutil::transition_image(cmd, postProcessor._fullscreenImage.image, VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL, - VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, VK_IMAGE_ASPECT_COLOR_BIT); + // Apply FXAA if enabled + if (postProcessor._compositorData.useFXAA) { + vkutil::transition_image(cmd, postProcessor._fullscreenImage.image, + VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, + VK_IMAGE_ASPECT_COLOR_BIT); + vkutil::transition_image(cmd, postProcessor._fxaaImage.image, VK_IMAGE_LAYOUT_UNDEFINED, + VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL, VK_IMAGE_ASPECT_COLOR_BIT); + + postProcessor.draw_fxaa(this, cmd); + + vkutil::transition_image(cmd, postProcessor._fxaaImage.image, VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL, + VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, VK_IMAGE_ASPECT_COLOR_BIT); + } else { + vkutil::transition_image(cmd, postProcessor._fullscreenImage.image, + VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, + VK_IMAGE_ASPECT_COLOR_BIT); + } } else { // Rasterization path - all the normal draw calls vkutil::transition_image(cmd, raytracerPipeline._rtOutputImage.image, VK_IMAGE_LAYOUT_GENERAL, @@ -481,13 +496,29 @@ void VulkanEngine::draw() { postProcessor.draw(this, cmd); - vkutil::transition_image(cmd, postProcessor._fullscreenImage.image, VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL, - VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, VK_IMAGE_ASPECT_COLOR_BIT); + // Apply FXAA if enabled + if (postProcessor._compositorData.useFXAA) { + vkutil::transition_image(cmd, postProcessor._fullscreenImage.image, + VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, + VK_IMAGE_ASPECT_COLOR_BIT); + vkutil::transition_image(cmd, postProcessor._fxaaImage.image, VK_IMAGE_LAYOUT_UNDEFINED, + VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL, VK_IMAGE_ASPECT_COLOR_BIT); + + postProcessor.draw_fxaa(this, cmd); + + vkutil::transition_image(cmd, postProcessor._fxaaImage.image, VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL, + VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, VK_IMAGE_ASPECT_COLOR_BIT); + } else { + vkutil::transition_image(cmd, postProcessor._fullscreenImage.image, + VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, + VK_IMAGE_ASPECT_COLOR_BIT); + } } - // Transition fullscreen image for ImGui viewport usage - vkutil::transition_image(cmd, postProcessor._fullscreenImage.image, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, + // Transition final processed image for ImGui viewport usage + const AllocatedImage &finalImage = postProcessor.getFinalImage(); + vkutil::transition_image(cmd, finalImage.image, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, VK_IMAGE_ASPECT_COLOR_BIT); // Prepare swapchain for UI (no scene copy - scene is displayed in ImGui viewport) @@ -1687,16 +1718,18 @@ void VulkanEngine::save_screenshot_render_only() { // Wait for current frame to complete vkDeviceWaitIdle(_device); + // Get the final processed image for screenshot + const AllocatedImage &finalImage = postProcessor.getFinalImage(); + // Create staging buffer for the render image AllocatedBuffer stagingBuffer = vkutil::create_buffer( this, - postProcessor._fullscreenImage.imageExtent.width * postProcessor._fullscreenImage.imageExtent.height * - 16, // 4 floats per pixel + finalImage.imageExtent.width * finalImage.imageExtent.height * 16, // 4 floats per pixel VK_BUFFER_USAGE_TRANSFER_DST_BIT, VMA_MEMORY_USAGE_CPU_ONLY, "Screenshot Render Staging Buffer"); immediate_submit([&](VkCommandBuffer cmd) { // Transition render image for reading - vkutil::transition_image(cmd, postProcessor._fullscreenImage.image, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, + vkutil::transition_image(cmd, finalImage.image, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, VK_IMAGE_ASPECT_COLOR_BIT); // Copy image to buffer @@ -1708,14 +1741,14 @@ void VulkanEngine::save_screenshot_render_only() { copyRegion.imageSubresource.mipLevel = 0; copyRegion.imageSubresource.baseArrayLayer = 0; copyRegion.imageSubresource.layerCount = 1; - copyRegion.imageExtent = postProcessor._fullscreenImage.imageExtent; + copyRegion.imageExtent = finalImage.imageExtent; copyRegion.imageOffset = {0, 0, 0}; - vkCmdCopyImageToBuffer(cmd, postProcessor._fullscreenImage.image, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, - stagingBuffer.buffer, 1, ©Region); + vkCmdCopyImageToBuffer(cmd, finalImage.image, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, stagingBuffer.buffer, 1, + ©Region); // Transition back - vkutil::transition_image(cmd, postProcessor._fullscreenImage.image, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, + vkutil::transition_image(cmd, finalImage.image, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, VK_IMAGE_ASPECT_COLOR_BIT); }); @@ -1734,12 +1767,9 @@ void VulkanEngine::save_screenshot_render_only() { // Convert HDR float data to LDR uint8 (raw conversion, no tone mapping) float *src = static_cast(data); - std::vector ldr_data(postProcessor._fullscreenImage.imageExtent.width * - postProcessor._fullscreenImage.imageExtent.height * 3); + std::vector ldr_data(finalImage.imageExtent.width * finalImage.imageExtent.height * 3); - for (uint32_t i = 0; - i < postProcessor._fullscreenImage.imageExtent.width * postProcessor._fullscreenImage.imageExtent.height; - i++) { + for (uint32_t i = 0; i < finalImage.imageExtent.width * finalImage.imageExtent.height; i++) { // Raw conversion - just clamp to 0-1 range and convert to 0-255 float r = src[i * 4 + 0]; float g = src[i * 4 + 1]; @@ -1750,9 +1780,8 @@ void VulkanEngine::save_screenshot_render_only() { ldr_data[i * 3 + 2] = static_cast(std::clamp(b * 255.0f, 0.0f, 255.0f)); } - if (stbi_write_png(ss.str().c_str(), postProcessor._fullscreenImage.imageExtent.width, - postProcessor._fullscreenImage.imageExtent.height, 3, ldr_data.data(), - postProcessor._fullscreenImage.imageExtent.width * 3)) { + if (stbi_write_png(ss.str().c_str(), finalImage.imageExtent.width, finalImage.imageExtent.height, 3, + ldr_data.data(), finalImage.imageExtent.width * 3)) { spdlog::info("Render-only screenshot saved: {}", ss.str()); } else { spdlog::error("Failed to save render-only screenshot: {}", ss.str()); diff --git a/VkRenderer/vk_engine.h b/VkRenderer/vk_engine.h index 1af88c7..32b1e47 100644 --- a/VkRenderer/vk_engine.h +++ b/VkRenderer/vk_engine.h @@ -196,6 +196,7 @@ class VulkanEngine { // Full screen quad resources PostProcessor postProcessor; VkDescriptorSet _viewportTextureDescriptorSet = VK_NULL_HANDLE; + VkDescriptorSet _fxaaViewportTextureDescriptorSet = VK_NULL_HANDLE; // immediate submit structures VkFence _immFence; diff --git a/shaders/FXAA.frag b/shaders/FXAA.frag new file mode 100644 index 0000000..b52041a --- /dev/null +++ b/shaders/FXAA.frag @@ -0,0 +1,58 @@ +/* + Implementation of FXAA (Fast Approximate Anti-Aliasing) based on the algorithm described by Timothy Lottes. + https://developer.download.nvidia.com/assets/gamedev/files/sdk/11/FXAA_WhitePaper.pdf +*/ + +#version 450 + +layout (location = 0) in vec2 inUV; + +layout (location = 0) out vec4 outColor; + +layout (set = 0, binding = 0) uniform sampler2D R_filterTexture; + +layout(set = 0, binding = 1) uniform FXAAData { + vec3 R_inverseFilterTextureSize; + float R_fxaaSpanMax; + float R_fxaaReduceMin; + float R_fxaaReduceMul; +} fxaaData; + +void main() +{ + vec2 texCoordOffset = fxaaData.R_inverseFilterTextureSize.xy; + + vec3 luma = vec3(0.299, 0.587, 0.114); + float lumaTL = dot(luma, texture(R_filterTexture, inUV + (vec2(-1.0, -1.0) * texCoordOffset)).xyz); + float lumaTR = dot(luma, texture(R_filterTexture, inUV + (vec2(1.0, -1.0) * texCoordOffset)).xyz); + float lumaBL = dot(luma, texture(R_filterTexture, inUV + (vec2(-1.0, 1.0) * texCoordOffset)).xyz); + float lumaBR = dot(luma, texture(R_filterTexture, inUV + (vec2(1.0, 1.0) * texCoordOffset)).xyz); + float lumaM = dot(luma, texture(R_filterTexture, inUV).xyz); + + vec2 dir; + dir.x = -((lumaTL + lumaTR) - (lumaBL + lumaBR)); + dir.y = ((lumaTL + lumaBL) - (lumaTR + lumaBR)); + + float dirReduce = max((lumaTL + lumaTR + lumaBL + lumaBR) * (fxaaData.R_fxaaReduceMul * 0.25), fxaaData.R_fxaaReduceMin); + float inverseDirAdjustment = 1.0/(min(abs(dir.x), abs(dir.y)) + dirReduce); + + dir = min(vec2(fxaaData.R_fxaaSpanMax, fxaaData.R_fxaaSpanMax), + max(vec2(-fxaaData.R_fxaaSpanMax, -fxaaData.R_fxaaSpanMax), dir * inverseDirAdjustment)) * texCoordOffset; + + vec3 result1 = (1.0/2.0) * ( + texture(R_filterTexture, inUV + (dir * vec2(1.0/3.0 - 0.5))).xyz + + texture(R_filterTexture, inUV + (dir * vec2(2.0/3.0 - 0.5))).xyz); + + vec3 result2 = result1 * (1.0/2.0) + (1.0/4.0) * ( + texture(R_filterTexture, inUV + (dir * vec2(0.0/3.0 - 0.5))).xyz + + texture(R_filterTexture, inUV + (dir * vec2(3.0/3.0 - 0.5))).xyz); + + float lumaMin = min(lumaM, min(min(lumaTL, lumaTR), min(lumaBL, lumaBR))); + float lumaMax = max(lumaM, max(max(lumaTL, lumaTR), max(lumaBL, lumaBR))); + float lumaResult2 = dot(luma, result2); + + if(lumaResult2 < lumaMin || lumaResult2 > lumaMax) + outColor = vec4(result1, 1.0); + else + outColor = vec4(result2, 1.0); +} \ No newline at end of file