1
0
mirror of https://github.com/doitsujin/dxvk.git synced 2025-02-27 22:54:16 +01:00

[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.
This commit is contained in:
Philip Rebohle 2018-04-13 13:47:15 +02:00
parent 4a0c9dbaba
commit 5d7c83855e
No known key found for this signature in database
GPG Key ID: C8CC613427A31C99
6 changed files with 238 additions and 153 deletions

View File

@ -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;
}

View File

@ -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<DxvkSampler> 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<DxvkBuffer> 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<DxvkImage> 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<DxvkImageView> 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<DxvkShader> 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<DxvkResourceSlot, 3> resourceSlots = {{
const std::array<DxvkResourceSlot, 5> 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 },
}};

View File

@ -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<DxvkDevice> m_device;
Rc<DxvkContext> m_context;
Rc<DxvkDevice> m_device;
Rc<DxvkContext> m_context;
Rc<DxvkSurface> m_surface;
Rc<DxvkSwapchain> m_swapchain;
Rc<DxvkSurface> m_surface;
Rc<DxvkSwapchain> m_swapchain;
Rc<DxvkBuffer> m_gammaBuffer;
Rc<DxvkSampler> m_samplerFitting;
Rc<DxvkSampler> m_samplerScaling;
Rc<DxvkSampler> m_samplerFitting;
Rc<DxvkSampler> m_samplerScaling;
Rc<DxvkImage> m_backBuffer;
Rc<DxvkImage> m_backBufferResolve;
Rc<DxvkImageView> m_backBufferView;
Rc<DxvkImage> m_backBuffer;
Rc<DxvkImage> m_backBufferResolve;
Rc<DxvkImageView> m_backBufferView;
Rc<DxvkBuffer> m_gammaUbo;
Rc<DxvkSampler> m_gammaSampler;
Rc<DxvkImage> m_gammaTexture;
Rc<DxvkImageView> m_gammaTextureView;
Rc<hud::Hud> m_hud;
Rc<hud::Hud> m_hud;
DxvkBlendMode m_blendMode;
DxvkSwapchainProperties m_options;
Rc<DxvkSampler> createSampler(
VkFilter filter,
VkSamplerAddressMode addressMode);
Rc<DxvkBuffer> createGammaUbo();
Rc<DxvkImage> createGammaTexture();
Rc<DxvkImageView> createGammaTextureView();
Rc<DxvkShader> createVertexShader();
Rc<DxvkShader> createFragmentShader();

View File

@ -281,47 +281,27 @@ namespace dxvk {
}
HRESULT DxgiSwapChain::GetGammaControl(DXGI_GAMMA_CONTROL* pGammaControl) {
std::lock_guard<std::recursive_mutex> 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<std::recursive_mutex> 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<std::recursive_mutex> 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;
}

View File

@ -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();

View File

@ -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);
}