From 5d7c83855eef00ab1bff9b80939ccc3abffa0813 Mon Sep 17 00:00:00 2001 From: Philip Rebohle Date: Fri, 13 Apr 2018 13:47:15 +0200 Subject: [PATCH] [dxgi] Use 1D texture to implement the gamma lookup table This allows us to abuse hardware texture filters for linear interpolation. Should fix an issue with the latest Nvidia beta drivers. --- src/dxgi/dxgi_output.cpp | 8 +- src/dxgi/dxgi_presenter.cpp | 139 ++++++++++++++++------ src/dxgi/dxgi_presenter.h | 122 ++++++++++++++----- src/dxgi/dxgi_swapchain.cpp | 74 ++++-------- src/dxgi/dxgi_swapchain.h | 5 - src/dxgi/shaders/dxgi_presenter_frag.frag | 43 +++---- 6 files changed, 238 insertions(+), 153 deletions(-) diff --git a/src/dxgi/dxgi_output.cpp b/src/dxgi/dxgi_output.cpp index d0d57a278..20305c14b 100644 --- a/src/dxgi/dxgi_output.cpp +++ b/src/dxgi/dxgi_output.cpp @@ -24,8 +24,8 @@ namespace dxvk { outputData.GammaCurve.Scale = { 1.0f, 1.0f, 1.0f }; outputData.GammaCurve.Offset = { 0.0f, 0.0f, 0.0f }; - for (uint32_t i = 0; i < DxgiPresenterGammaRamp::CpCount; i++) { - const float value = DxgiPresenterGammaRamp::cpLocation(i); + for (uint32_t i = 0; i < DXGI_VK_GAMMA_CP_COUNT; i++) { + const float value = GammaControlPointLocation(i); outputData.GammaCurve.GammaCurve[i] = { value, value, value }; } @@ -272,10 +272,10 @@ namespace dxvk { pGammaCaps->ScaleAndOffsetSupported = TRUE; pGammaCaps->MaxConvertedValue = 1.0f; pGammaCaps->MinConvertedValue = 0.0f; - pGammaCaps->NumGammaControlPoints = DxgiPresenterGammaRamp::CpCount; + pGammaCaps->NumGammaControlPoints = DXGI_VK_GAMMA_CP_COUNT; for (uint32_t i = 0; i < pGammaCaps->NumGammaControlPoints; i++) - pGammaCaps->ControlPointPositions[i] = DxgiPresenterGammaRamp::cpLocation(i); + pGammaCaps->ControlPointPositions[i] = GammaControlPointLocation(i); return S_OK; } diff --git a/src/dxgi/dxgi_presenter.cpp b/src/dxgi/dxgi_presenter.cpp index ad8c0ea0d..308f26ba0 100644 --- a/src/dxgi/dxgi_presenter.cpp +++ b/src/dxgi/dxgi_presenter.cpp @@ -25,40 +25,19 @@ namespace dxvk { m_options.preferredPresentMode = VK_PRESENT_MODE_FIFO_KHR; m_options.preferredBufferSize = { 0u, 0u }; - // Uniform buffer that stores the gamma ramp - DxvkBufferCreateInfo gammaBufferInfo; - gammaBufferInfo.size = sizeof(DxgiPresenterGammaRamp); - gammaBufferInfo.usage = VK_BUFFER_USAGE_TRANSFER_DST_BIT - | VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT; - gammaBufferInfo.stages = VK_PIPELINE_STAGE_TRANSFER_BIT - | VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT; - gammaBufferInfo.access = VK_ACCESS_TRANSFER_WRITE_BIT - | VK_ACCESS_SHADER_READ_BIT; - m_gammaBuffer = m_device->createBuffer( - gammaBufferInfo, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT); + // Samplers for presentation. We'll create one with point sampling that will + // be used when the back buffer resolution matches the output resolution, and + // one with linar sampling that will be used when the image will be scaled. + m_samplerFitting = this->createSampler(VK_FILTER_NEAREST, VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_BORDER); + m_samplerScaling = this->createSampler(VK_FILTER_LINEAR, VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_BORDER); - // Sampler for presentation - DxvkSamplerCreateInfo samplerInfo; - samplerInfo.magFilter = VK_FILTER_NEAREST; - samplerInfo.minFilter = VK_FILTER_NEAREST; - samplerInfo.mipmapMode = VK_SAMPLER_MIPMAP_MODE_NEAREST; - samplerInfo.mipmapLodBias = 0.0f; - samplerInfo.mipmapLodMin = 0.0f; - samplerInfo.mipmapLodMax = 0.0f; - samplerInfo.useAnisotropy = VK_FALSE; - samplerInfo.maxAnisotropy = 1.0f; - samplerInfo.addressModeU = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_BORDER; - samplerInfo.addressModeV = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_BORDER; - samplerInfo.addressModeW = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_BORDER; - samplerInfo.compareToDepth = VK_FALSE; - samplerInfo.compareOp = VK_COMPARE_OP_ALWAYS; - samplerInfo.borderColor = VK_BORDER_COLOR_FLOAT_TRANSPARENT_BLACK; - samplerInfo.usePixelCoord = VK_FALSE; - m_samplerFitting = m_device->createSampler(samplerInfo); - - samplerInfo.magFilter = VK_FILTER_LINEAR; - samplerInfo.minFilter = VK_FILTER_LINEAR; - m_samplerScaling = m_device->createSampler(samplerInfo); + // Create objects required for the gamma ramp. This is implemented partially + // with an UBO, which stores global parameters, and a lookup texture, which + // stores the actual gamma ramp and can be sampled with a linear filter. + m_gammaUbo = this->createGammaUbo(); + m_gammaSampler = this->createSampler(VK_FILTER_LINEAR, VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE); + m_gammaTexture = this->createGammaTexture(); + m_gammaTextureView = this->createGammaTextureView(); // Set up context state. The shader bindings and the // constant state objects will never be modified. @@ -223,7 +202,9 @@ namespace dxvk { m_context->bindResourceView(BindingIds::Texture, m_backBufferView, nullptr); m_context->draw(4, 1, 0, 0); - m_context->bindResourceBuffer(BindingIds::GammaUbo, DxvkBufferSlice(m_gammaBuffer)); + m_context->bindResourceSampler(BindingIds::GammaSmp, m_gammaSampler); + m_context->bindResourceView (BindingIds::GammaTex, m_gammaTextureView, nullptr); + m_context->bindResourceBuffer (BindingIds::GammaUbo, DxvkBufferSlice(m_gammaUbo)); if (m_hud != nullptr) { m_blendMode.enableBlending = VK_TRUE; @@ -359,13 +340,21 @@ namespace dxvk { } - void DxgiPresenter::setGammaRamp(const DxgiPresenterGammaRamp& data) { + void DxgiPresenter::setGammaControl( + const DXGI_VK_GAMMA_INPUT_CONTROL* pGammaControl, + const DXGI_VK_GAMMA_CURVE* pGammaCurve) { m_context->beginRecording( m_device->createCommandList()); - m_context->updateBuffer(m_gammaBuffer, - 0, sizeof(DxgiPresenterGammaRamp), - &data); + m_context->updateBuffer(m_gammaUbo, + 0, sizeof(DXGI_VK_GAMMA_INPUT_CONTROL), + pGammaControl); + + m_context->updateImage(m_gammaTexture, + VkImageSubresourceLayers { VK_IMAGE_ASPECT_COLOR_BIT, 0, 0, 1 }, + VkOffset3D { 0, 0, 0 }, + VkExtent3D { DXGI_VK_GAMMA_CP_COUNT, 1, 1 }, + pGammaCurve, 0, 0); m_device->submitCommandList( m_context->endRecording(), @@ -373,6 +362,76 @@ namespace dxvk { } + Rc DxgiPresenter::createSampler( + VkFilter filter, + VkSamplerAddressMode addressMode) { + DxvkSamplerCreateInfo samplerInfo; + samplerInfo.magFilter = filter; + samplerInfo.minFilter = filter; + samplerInfo.mipmapMode = VK_SAMPLER_MIPMAP_MODE_NEAREST; + samplerInfo.mipmapLodBias = 0.0f; + samplerInfo.mipmapLodMin = 0.0f; + samplerInfo.mipmapLodMax = 0.0f; + samplerInfo.useAnisotropy = VK_FALSE; + samplerInfo.maxAnisotropy = 1.0f; + samplerInfo.addressModeU = addressMode; + samplerInfo.addressModeV = addressMode; + samplerInfo.addressModeW = addressMode; + samplerInfo.compareToDepth = VK_FALSE; + samplerInfo.compareOp = VK_COMPARE_OP_ALWAYS; + samplerInfo.borderColor = VK_BORDER_COLOR_FLOAT_TRANSPARENT_BLACK; + samplerInfo.usePixelCoord = VK_FALSE; + return m_device->createSampler(samplerInfo); + } + + + Rc DxgiPresenter::createGammaUbo() { + DxvkBufferCreateInfo info; + info.size = sizeof(DXGI_VK_GAMMA_INPUT_CONTROL); + info.usage = VK_BUFFER_USAGE_TRANSFER_DST_BIT + | VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT; + info.stages = VK_PIPELINE_STAGE_TRANSFER_BIT + | VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT; + info.access = VK_ACCESS_TRANSFER_WRITE_BIT + | VK_ACCESS_SHADER_READ_BIT; + return m_device->createBuffer(info, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT); + } + + + Rc DxgiPresenter::createGammaTexture() { + DxvkImageCreateInfo info; + info.type = VK_IMAGE_TYPE_1D; + info.format = VK_FORMAT_R16G16B16A16_UNORM; + info.flags = 0; + info.sampleCount = VK_SAMPLE_COUNT_1_BIT; + info.extent = { DXGI_VK_GAMMA_CP_COUNT, 1, 1 }; + info.numLayers = 1; + info.mipLevels = 1; + info.usage = VK_IMAGE_USAGE_TRANSFER_DST_BIT + | VK_IMAGE_USAGE_SAMPLED_BIT; + info.stages = VK_PIPELINE_STAGE_TRANSFER_BIT + | VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT; + info.access = VK_ACCESS_TRANSFER_WRITE_BIT + | VK_ACCESS_SHADER_READ_BIT; + info.tiling = VK_IMAGE_TILING_OPTIMAL; + info.layout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL; + return m_device->createImage(info, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT); + } + + + Rc DxgiPresenter::createGammaTextureView() { + DxvkImageViewCreateInfo info; + info.type = VK_IMAGE_VIEW_TYPE_1D; + info.format = VK_FORMAT_R16G16B16A16_UNORM; + info.aspect = VK_IMAGE_ASPECT_COLOR_BIT; + info.minLevel = 0; + info.numLevels = 1; + info.minLayer = 0; + info.numLayers = 1; + return m_device->createImageView(m_gammaTexture, info); + } + + Rc DxgiPresenter::createVertexShader() { const SpirvCodeBuffer codeBuffer(dxgi_presenter_vert); @@ -387,9 +446,11 @@ namespace dxvk { const SpirvCodeBuffer codeBuffer(dxgi_presenter_frag); // Shader resource slots - const std::array resourceSlots = {{ + const std::array resourceSlots = {{ { BindingIds::Sampler, VK_DESCRIPTOR_TYPE_SAMPLER, VK_IMAGE_VIEW_TYPE_MAX_ENUM }, { BindingIds::Texture, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, VK_IMAGE_VIEW_TYPE_2D }, + { BindingIds::GammaSmp, VK_DESCRIPTOR_TYPE_SAMPLER, VK_IMAGE_VIEW_TYPE_MAX_ENUM }, + { BindingIds::GammaTex, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, VK_IMAGE_VIEW_TYPE_1D }, { BindingIds::GammaUbo, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, VK_IMAGE_VIEW_TYPE_MAX_ENUM }, }}; diff --git a/src/dxgi/dxgi_presenter.h b/src/dxgi/dxgi_presenter.h index 76d75d021..bdbbf66a4 100644 --- a/src/dxgi/dxgi_presenter.h +++ b/src/dxgi/dxgi_presenter.h @@ -12,25 +12,71 @@ namespace dxvk { + constexpr uint32_t DXGI_VK_GAMMA_CP_COUNT = 1024; + /** - * \brief Gamma ramp + * \brief Gamma control point * - * Structure that can be used to set the gamma - * ramp of a swap chain. This is the same data - * structure that is used by the fragment shader. + * Control points are stored as normalized + * 16-bit unsigned integer values that will + * be converted back to floats in the shader. */ - struct DxgiPresenterGammaRamp { - constexpr static uint32_t CpCount = 1025; - - float in_factor[4]; - float in_offset[4]; - float cp_values[4 * CpCount]; - - static float cpLocation(uint32_t cp) { - return float(cp) / float(CpCount - 1); - } + struct DXGI_VK_GAMMA_CP { + uint16_t R, G, B, A; }; + /** + * \brief Gamma curve + * + * A collection of control points that + * will be uploaded to the gamma texture. + */ + struct DXGI_VK_GAMMA_CURVE { + DXGI_VK_GAMMA_CP ControlPoints[DXGI_VK_GAMMA_CP_COUNT]; + }; + + /** + * \brief Gamma input color + * A floating-point color vector. + */ + struct DXGI_VK_GAMMA_INPUT_COLOR { + float R, G, B, A; + }; + + /** + * \brief Gamma input control + * + * Stores a scaling factor and a bias that shall + * be applied to the input color before performing + * the gamma lookup in the fragment shader. + */ + struct DXGI_VK_GAMMA_INPUT_CONTROL { + DXGI_VK_GAMMA_INPUT_COLOR Factor; + DXGI_VK_GAMMA_INPUT_COLOR Offset; + }; + + /** + * \brief Maps color value to normalized integer + * + * \param [in] x Input value, as floating point + * \returns Corresponding normalized integer + */ + inline uint16_t MapGammaControlPoint(float x) { + if (x < 0.0f) x = 0.0f; + if (x > 1.0f) x = 1.0f; + return uint16_t(65535.0f * x); + } + + /** + * \brief Computes gamma control point location + * + * \param [in] CpIndex Control point ID + * \returns Location of the control point + */ + inline float GammaControlPointLocation(uint32_t CpIndex) { + return float(CpIndex) / float(DXGI_VK_GAMMA_CP_COUNT - 1); + } + /** * \brief DXGI presenter * @@ -100,39 +146,57 @@ namespace dxvk { VkPresentModeKHR pickPresentMode(VkPresentModeKHR preferred) const; /** - * \brief Sets gamma ramp - * \param [in] data Gamma data + * \brief Sets gamma curve + * + * Updates the gamma lookup texture. + * \param [in] pGammaControl Input parameters + * \param [in] pGammaCurve Gamma curve */ - void setGammaRamp(const DxgiPresenterGammaRamp& data); + void setGammaControl( + const DXGI_VK_GAMMA_INPUT_CONTROL* pGammaControl, + const DXGI_VK_GAMMA_CURVE* pGammaCurve); private: enum BindingIds : uint32_t { Sampler = 0, Texture = 1, - GammaUbo = 2, + GammaSmp = 2, + GammaTex = 3, + GammaUbo = 4, }; - Rc m_device; - Rc m_context; + Rc m_device; + Rc m_context; - Rc m_surface; - Rc m_swapchain; + Rc m_surface; + Rc m_swapchain; - Rc m_gammaBuffer; + Rc m_samplerFitting; + Rc m_samplerScaling; - Rc m_samplerFitting; - Rc m_samplerScaling; + Rc m_backBuffer; + Rc m_backBufferResolve; + Rc m_backBufferView; - Rc m_backBuffer; - Rc m_backBufferResolve; - Rc m_backBufferView; + Rc m_gammaUbo; + Rc m_gammaSampler; + Rc m_gammaTexture; + Rc m_gammaTextureView; - Rc m_hud; + Rc m_hud; DxvkBlendMode m_blendMode; DxvkSwapchainProperties m_options; + Rc createSampler( + VkFilter filter, + VkSamplerAddressMode addressMode); + + Rc createGammaUbo(); + Rc createGammaTexture(); + Rc createGammaTextureView(); + Rc createVertexShader(); Rc createFragmentShader(); diff --git a/src/dxgi/dxgi_swapchain.cpp b/src/dxgi/dxgi_swapchain.cpp index d7a1f3bf0..2e7e004cd 100644 --- a/src/dxgi/dxgi_swapchain.cpp +++ b/src/dxgi/dxgi_swapchain.cpp @@ -281,47 +281,27 @@ namespace dxvk { } - HRESULT DxgiSwapChain::GetGammaControl(DXGI_GAMMA_CONTROL* pGammaControl) { - std::lock_guard lock(m_mutex); - - pGammaControl->Scale = { - m_gammaControl.in_factor[0], - m_gammaControl.in_factor[1], - m_gammaControl.in_factor[2] }; - - pGammaControl->Offset = { - m_gammaControl.in_offset[0], - m_gammaControl.in_offset[1], - m_gammaControl.in_offset[2] }; - - for (uint32_t i = 0; i < DxgiPresenterGammaRamp::CpCount; i++) { - pGammaControl->GammaCurve[i] = { - m_gammaControl.cp_values[4 * i + 0], - m_gammaControl.cp_values[4 * i + 1], - m_gammaControl.cp_values[4 * i + 2] }; - } - - return S_OK; - } - - HRESULT DxgiSwapChain::SetGammaControl(const DXGI_GAMMA_CONTROL* pGammaControl) { std::lock_guard lock(m_mutex); - m_gammaControl.in_factor[0] = pGammaControl->Scale.Red; - m_gammaControl.in_factor[1] = pGammaControl->Scale.Green; - m_gammaControl.in_factor[2] = pGammaControl->Scale.Blue; - m_gammaControl.in_offset[0] = pGammaControl->Offset.Red; - m_gammaControl.in_offset[1] = pGammaControl->Offset.Green; - m_gammaControl.in_offset[2] = pGammaControl->Offset.Blue; + const DXGI_RGB Factor = pGammaControl->Scale; + const DXGI_RGB Offset = pGammaControl->Offset; - for (uint32_t i = 0; i < DxgiPresenterGammaRamp::CpCount; i++) { - m_gammaControl.cp_values[4 * i + 0] = pGammaControl->GammaCurve[i].Red; - m_gammaControl.cp_values[4 * i + 1] = pGammaControl->GammaCurve[i].Green; - m_gammaControl.cp_values[4 * i + 2] = pGammaControl->GammaCurve[i].Blue; + DXGI_VK_GAMMA_INPUT_CONTROL control; + control.Factor = { Factor.Red, Factor.Green, Factor.Blue, 1.0f }; + control.Offset = { Offset.Red, Offset.Green, Offset.Blue, 0.0f }; + + DXGI_VK_GAMMA_CURVE curve; + + for (uint32_t i = 0; i < DXGI_VK_GAMMA_CP_COUNT; i++) { + const DXGI_RGB cp = pGammaControl->GammaCurve[i]; + curve.ControlPoints[i].R = MapGammaControlPoint(cp.Red); + curve.ControlPoints[i].G = MapGammaControlPoint(cp.Green); + curve.ControlPoints[i].B = MapGammaControlPoint(cp.Blue); + curve.ControlPoints[i].A = 0; } - m_presenter->setGammaRamp(m_gammaControl); + m_presenter->setGammaControl(&control, &curve); return S_OK; } @@ -329,21 +309,19 @@ namespace dxvk { HRESULT DxgiSwapChain::SetDefaultGammaControl() { std::lock_guard lock(m_mutex); - for (uint32_t i = 0; i < 4; i++) { - m_gammaControl.in_factor[i] = 1.0f; - m_gammaControl.in_offset[i] = 0.0f; + DXGI_VK_GAMMA_INPUT_CONTROL control; + control.Factor = { 1.0f, 1.0f, 1.0f, 1.0f }; + control.Offset = { 0.0f, 0.0f, 0.0f, 0.0f }; + + DXGI_VK_GAMMA_CURVE curve; + + for (uint32_t i = 0; i < DXGI_VK_GAMMA_CP_COUNT; i++) { + const uint16_t value = MapGammaControlPoint( + float(i) / float(DXGI_VK_GAMMA_CP_COUNT - 1)); + curve.ControlPoints[i] = { value, value, value, 0 }; } - for (uint32_t i = 0; i < DxgiPresenterGammaRamp::CpCount; i++) { - const float value = DxgiPresenterGammaRamp::cpLocation(i); - - m_gammaControl.cp_values[4 * i + 0] = value; - m_gammaControl.cp_values[4 * i + 1] = value; - m_gammaControl.cp_values[4 * i + 2] = value; - m_gammaControl.cp_values[4 * i + 3] = value; - } - - m_presenter->setGammaRamp(m_gammaControl); + m_presenter->setGammaControl(&control, &curve); return S_OK; } diff --git a/src/dxgi/dxgi_swapchain.h b/src/dxgi/dxgi_swapchain.h index 19f26d17b..e3d8a55e1 100644 --- a/src/dxgi/dxgi_swapchain.h +++ b/src/dxgi/dxgi_swapchain.h @@ -81,9 +81,6 @@ namespace dxvk { BOOL Fullscreen, IDXGIOutput *pTarget) final; - HRESULT GetGammaControl( - DXGI_GAMMA_CONTROL* pGammaControl); - HRESULT SetGammaControl( const DXGI_GAMMA_CONTROL* pGammaControl); @@ -113,8 +110,6 @@ namespace dxvk { HMONITOR m_monitor; WindowState m_windowState; - DxgiPresenterGammaRamp m_gammaControl; - HRESULT CreatePresenter(); HRESULT CreateBackBuffer(); diff --git a/src/dxgi/shaders/dxgi_presenter_frag.frag b/src/dxgi/shaders/dxgi_presenter_frag.frag index 498594670..28cad1391 100644 --- a/src/dxgi/shaders/dxgi_presenter_frag.frag +++ b/src/dxgi/shaders/dxgi_presenter_frag.frag @@ -1,43 +1,30 @@ #version 450 -#define CP_COUNT 1025 - layout(binding = 0) uniform sampler s_sampler; layout(binding = 1) uniform texture2D t_texture; -layout(binding = 2) -uniform u_gamma_ramp_t { +layout(binding = 2) uniform sampler s_gamma; +layout(binding = 3) uniform texture1D t_gamma; + +layout(binding = 4) +uniform u_gamma_info_t { layout(offset = 0) vec4 in_factor; layout(offset = 16) vec4 in_offset; - layout(offset = 32) vec4 cp_values[CP_COUNT + 1]; -} u_gamma_ramp; +} u_gamma_info; layout(location = 0) in vec2 i_texcoord; layout(location = 0) out vec4 o_color; void main() { - o_color = texture(sampler2D(t_texture, s_sampler), i_texcoord); + vec4 color = texture(sampler2D(t_texture, s_sampler), i_texcoord); - vec3 cp_lookup = o_color.rgb; - cp_lookup *= u_gamma_ramp.in_factor.rgb; - cp_lookup += u_gamma_ramp.in_offset.rgb; + vec3 cp_lookup = color.rgb; + cp_lookup *= u_gamma_info.in_factor.rgb; + cp_lookup += u_gamma_info.in_offset.rgb; - cp_lookup = clamp( - cp_lookup * float(CP_COUNT - 1), - 0.0f, float(CP_COUNT - 1)); - - vec3 cp_fpart = fract(cp_lookup); - ivec3 cp_index = ivec3(cp_lookup); - - for (int i = 0; i < 3; i++) { - int cp_entry = cp_index[i]; - - float lo = u_gamma_ramp.cp_values[cp_entry + 0][i]; - float hi = u_gamma_ramp.cp_values[cp_entry + 1][i]; - - if (cp_entry == CP_COUNT - 1) - hi = lo; - - o_color[i] = mix(lo, hi, cp_fpart[i]); - } + o_color = vec4( + texture(sampler1D(t_gamma, s_gamma), cp_lookup.r).r, + texture(sampler1D(t_gamma, s_gamma), cp_lookup.g).g, + texture(sampler1D(t_gamma, s_gamma), cp_lookup.b).b, + color.a); } \ No newline at end of file