color, shaders: Use color mgmt LUT path in shader compute path

This commit is contained in:
Joshua Ashton 2023-04-15 10:47:57 +01:00 committed by Joshie
parent c6e232ea8c
commit 0c980fbb0e
11 changed files with 410 additions and 203 deletions

View file

@ -1866,11 +1866,11 @@ struct LiftoffStateCacheEntryKasher
std::unordered_set<LiftoffStateCacheEntry, LiftoffStateCacheEntryKasher> g_LiftoffStateCache;
static inline drm_valve1_transfer_function colorspace_to_degamma_tf(GamescopeAppTextureColorspace colorspace)
static inline drm_valve1_transfer_function colorspace_to_plane_degamma_tf(GamescopeAppTextureColorspace colorspace)
{
switch ( colorspace )
{
default:
default: // Linear in this sense is SRGB. Linear = sRGB image view doing automatic sRGB -> Linear which doesn't happen on DRM side.
case GAMESCOPE_APP_TEXTURE_COLORSPACE_SRGB:
return DRM_VALVE1_TRANSFER_FUNCTION_SRGB;
case GAMESCOPE_APP_TEXTURE_COLORSPACE_SCRGB:
@ -1884,7 +1884,7 @@ static inline drm_valve1_transfer_function colorspace_to_degamma_tf(GamescopeApp
}
}
static inline drm_valve1_transfer_function colorspace_to_regamma_tf(GamescopeAppTextureColorspace colorspace)
static inline drm_valve1_transfer_function colorspace_to_plane_shaper_tf(GamescopeAppTextureColorspace colorspace)
{
switch ( colorspace )
{
@ -1908,10 +1908,8 @@ static inline uint32_t ColorSpaceToEOTFIndex( GamescopeAppTextureColorspace colo
return EOTF_Gamma22;
case GAMESCOPE_APP_TEXTURE_COLORSPACE_SCRGB:
// Okay, so this is WEIRD right? OKAY Let me explain it to you.
// The plan for scRGB content is to go from scRGB -> PQ in a DEGAMMA_LUT
// I know I know this sounds crazy, but this then goes into the same shaper + 3D LUT
// path as everything else does.
// TODO(Josh): Hook up DEGAMMA_LUT + DEGAMMA_TF to actually do re-gamma.
// The plan for scRGB content is to go from scRGB -> PQ in a SHAPER_TF
// before indexing into the shaper. (input from colorspace_to_plane_regamma_tf!)
return EOTF_PQ;
case GAMESCOPE_APP_TEXTURE_COLORSPACE_HDR10_PQ:
return EOTF_PQ;
@ -2048,12 +2046,13 @@ drm_prepare_liftoff( struct drm_t *drm, const struct FrameInfo_t *frameInfo, boo
liftoff_layer_unset_property( drm->lo_layers[ i ], "COLOR_RANGE" );
if ( drm_supports_color_mgmt( drm ) )
{
drm_valve1_transfer_function degamma_tf = colorspace_to_degamma_tf( entry.layerState[i].colorspace );
drm_valve1_transfer_function regamma_tf = colorspace_to_regamma_tf( entry.layerState[i].colorspace );
drm_valve1_transfer_function degamma_tf = colorspace_to_plane_degamma_tf( entry.layerState[i].colorspace );
drm_valve1_transfer_function shaper_tf = colorspace_to_plane_shaper_tf( entry.layerState[i].colorspace );
liftoff_layer_set_property( drm->lo_layers[ i ], "VALVE1_PLANE_DEGAMMA_TF", degamma_tf );
liftoff_layer_set_property( drm->lo_layers[ i ], "VALVE1_PLANE_SHAPER_LUT", drm->pending.shaperlut_id[ ColorSpaceToEOTFIndex( entry.layerState[i].colorspace ) ] );
liftoff_layer_set_property( drm->lo_layers[ i ], "VALVE1_PLANE_SHAPER_TF", regamma_tf );
liftoff_layer_set_property( drm->lo_layers[ i ], "VALVE1_PLANE_SHAPER_TF", shaper_tf );
liftoff_layer_set_property( drm->lo_layers[ i ], "VALVE1_PLANE_LUT3D", drm->pending.lut3d_id[ ColorSpaceToEOTFIndex( entry.layerState[i].colorspace ) ] );
// Josh: See shaders/colorimetry.h colorspace_blend_tf if you have questions as to why we start doing sRGB for BLEND_TF despite potentially working in Gamma 2.2 space prior.
liftoff_layer_set_property( drm->lo_layers[ i ], "VALVE1_PLANE_BLEND_TF", drm->pending.output_tf );
}
}

View file

@ -175,8 +175,7 @@ struct PipelineInfo_t
uint32_t compositeDebug;
uint32_t colorspaceMask;
bool st2084Output;
bool forceWideGammut;
uint32_t outputEOTF;
bool itmEnable;
bool operator==(const PipelineInfo_t& o) const {
@ -187,8 +186,7 @@ struct PipelineInfo_t
blurLayerCount == o.blurLayerCount &&
compositeDebug == o.compositeDebug &&
colorspaceMask == o.colorspaceMask &&
st2084Output == o.st2084Output &&
forceWideGammut == o.forceWideGammut &&
outputEOTF == o.outputEOTF &&
itmEnable == o.itmEnable;
}
};
@ -211,8 +209,7 @@ namespace std
hash = hash_combine(hash, k.blurLayerCount);
hash = hash_combine(hash, k.compositeDebug);
hash = hash_combine(hash, k.colorspaceMask);
hash = hash_combine(hash, k.st2084Output);
hash = hash_combine(hash, k.forceWideGammut);
hash = hash_combine(hash, k.outputEOTF);
hash = hash_combine(hash, k.itmEnable);
return hash;
}
@ -382,6 +379,7 @@ public:
void begin();
void end();
void bindTexture(uint32_t slot, std::shared_ptr<CVulkanTexture> texture);
void bindColorMgmtLuts(uint32_t slot, const std::shared_ptr<CVulkanTexture>& lut1d, const std::shared_ptr<CVulkanTexture>& lut3d);
void setTextureStorage(bool storage);
void setTextureSrgb(uint32_t slot, bool srgb);
void setSamplerNearest(uint32_t slot, bool nearest);
@ -414,6 +412,9 @@ private:
std::bitset<VKR_SAMPLER_SLOTS> m_useSrgb;
std::array<SamplerState, VKR_SAMPLER_SLOTS> m_samplerState;
CVulkanTexture *m_target;
std::array<CVulkanTexture *, VKR_LUT3D_COUNT> m_shaperLut;
std::array<CVulkanTexture *, VKR_LUT3D_COUNT> m_lut3D;
};
#define VULKAN_INSTANCE_FUNCTIONS \
@ -495,7 +496,7 @@ public:
bool BInit();
VkSampler sampler(SamplerState key);
VkPipeline pipeline(ShaderType type, uint32_t layerCount = 1, uint32_t ycbcrMask = 0, uint32_t blur_layers = 0, uint32_t colorspace_mask = 0, bool st2084_output = false, bool force_wide_gammut = false, bool itm_enable = false);
VkPipeline pipeline(ShaderType type, uint32_t layerCount = 1, uint32_t ycbcrMask = 0, uint32_t blur_layers = 0, uint32_t colorspace_mask = 0, uint32_t output_eotf = EOTF_Gamma22, bool itm_enable = false);
int32_t findMemoryType( VkMemoryPropertyFlags properties, uint32_t requiredTypeBits );
std::unique_ptr<CVulkanCmdBuffer> commandBuffer();
uint64_t submit( std::unique_ptr<CVulkanCmdBuffer> cmdBuf);
@ -541,7 +542,7 @@ private:
bool createPools();
bool createShaders();
bool createScratchResources();
VkPipeline compilePipeline(uint32_t layerCount, uint32_t ycbcrMask, ShaderType type, uint32_t blur_layer_count, uint32_t composite_debug, uint32_t colorspace_mask, bool st2084_output, bool force_wide_gammut, bool itm_enable);
VkPipeline compilePipeline(uint32_t layerCount, uint32_t ycbcrMask, ShaderType type, uint32_t blur_layer_count, uint32_t composite_debug, uint32_t colorspace_mask, uint32_t output_eotf, bool itm_enable);
void compileAllPipelines();
void resetCmdBuffers(uint64_t sequence);
@ -1003,7 +1004,7 @@ bool CVulkanDevice::createLayouts()
for (auto& sampler : ycbcrSamplers)
sampler = m_ycbcrSampler;
std::array<VkDescriptorSetLayoutBinding, 4> layoutBindings = {
std::array<VkDescriptorSetLayoutBinding, 6 > layoutBindings = {
VkDescriptorSetLayoutBinding {
.binding = 0,
.descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_IMAGE,
@ -1029,6 +1030,18 @@ bool CVulkanDevice::createLayouts()
.stageFlags = VK_SHADER_STAGE_COMPUTE_BIT,
.pImmutableSamplers = ycbcrSamplers.data(),
},
VkDescriptorSetLayoutBinding {
.binding = 4,
.descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER,
.descriptorCount = VKR_LUT3D_COUNT,
.stageFlags = VK_SHADER_STAGE_COMPUTE_BIT,
},
VkDescriptorSetLayoutBinding {
.binding = 5,
.descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER,
.descriptorCount = VKR_LUT3D_COUNT,
.stageFlags = VK_SHADER_STAGE_COMPUTE_BIT,
},
};
VkDescriptorSetLayoutCreateInfo descriptorSetLayoutCreateInfo =
@ -1091,7 +1104,7 @@ bool CVulkanDevice::createPools()
},
{
VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER,
uint32_t(m_descriptorSets.size()) * 2 * VKR_SAMPLER_SLOTS,
uint32_t(m_descriptorSets.size()) * ((2 * VKR_SAMPLER_SLOTS) + (2 * VKR_LUT3D_COUNT)),
},
};
@ -1264,9 +1277,9 @@ VkSampler CVulkanDevice::sampler( SamplerState key )
return ret;
}
VkPipeline CVulkanDevice::compilePipeline(uint32_t layerCount, uint32_t ycbcrMask, ShaderType type, uint32_t blur_layer_count, uint32_t composite_debug, uint32_t colorspace_mask, bool st2084_output, bool force_wide_gammut, bool itm_enable)
VkPipeline CVulkanDevice::compilePipeline(uint32_t layerCount, uint32_t ycbcrMask, ShaderType type, uint32_t blur_layer_count, uint32_t composite_debug, uint32_t colorspace_mask, uint32_t output_eotf, bool itm_enable)
{
const std::array<VkSpecializationMapEntry, 8> specializationEntries = {{
const std::array<VkSpecializationMapEntry, 7> specializationEntries = {{
{
.constantID = 0,
.offset = sizeof(uint32_t) * 0,
@ -1304,12 +1317,6 @@ VkPipeline CVulkanDevice::compilePipeline(uint32_t layerCount, uint32_t ycbcrMas
.offset = sizeof(uint32_t) * 6,
.size = sizeof(uint32_t)
},
{
.constantID = 7,
.offset = sizeof(uint32_t) * 7,
.size = sizeof(uint32_t)
},
}};
struct {
@ -1318,8 +1325,7 @@ VkPipeline CVulkanDevice::compilePipeline(uint32_t layerCount, uint32_t ycbcrMas
uint32_t debug;
uint32_t blur_layer_count;
uint32_t colorspace_mask;
uint32_t st2084_output;
uint32_t force_wide_gammut;
uint32_t output_eotf;
uint32_t itm_enable;
} specializationData = {
.layerCount = layerCount,
@ -1327,8 +1333,7 @@ VkPipeline CVulkanDevice::compilePipeline(uint32_t layerCount, uint32_t ycbcrMas
.debug = composite_debug,
.blur_layer_count = blur_layer_count,
.colorspace_mask = colorspace_mask,
.st2084_output = st2084_output,
.force_wide_gammut = force_wide_gammut,
.output_eotf = output_eotf,
.itm_enable = itm_enable,
};
@ -1367,7 +1372,7 @@ void CVulkanDevice::compileAllPipelines()
pthread_setname_np( pthread_self(), "gamescope-shdr" );
std::array<PipelineInfo_t, SHADER_TYPE_COUNT> pipelineInfos;
#define SHADER(type, layer_count, max_ycbcr, blur_layers) pipelineInfos[SHADER_TYPE_##type] = {SHADER_TYPE_##type, layer_count, max_ycbcr, blur_layers, false}
#define SHADER(type, layer_count, max_ycbcr, blur_layers) pipelineInfos[SHADER_TYPE_##type] = {SHADER_TYPE_##type, layer_count, max_ycbcr, blur_layers}
SHADER(BLIT, k_nMaxLayers, k_nMaxYcbcrMask_ToPreCompile, 1);
SHADER(BLUR, k_nMaxLayers, k_nMaxYcbcrMask_ToPreCompile, k_nMaxBlurLayers);
SHADER(BLUR_COND, k_nMaxLayers, k_nMaxYcbcrMask_ToPreCompile, k_nMaxBlurLayers);
@ -1387,7 +1392,7 @@ void CVulkanDevice::compileAllPipelines()
if (blur_layers > layerCount)
continue;
VkPipeline newPipeline = compilePipeline(layerCount, ycbcrMask, info.shaderType, blur_layers, info.compositeDebug, info.colorspaceMask, info.st2084Output, info.forceWideGammut, info.itmEnable);
VkPipeline newPipeline = compilePipeline(layerCount, ycbcrMask, info.shaderType, blur_layers, info.compositeDebug, info.colorspaceMask, info.outputEOTF, info.itmEnable);
{
std::lock_guard<std::mutex> lock(m_pipelineMutex);
PipelineInfo_t key = {info.shaderType, layerCount, ycbcrMask, blur_layers, info.compositeDebug};
@ -1401,14 +1406,14 @@ void CVulkanDevice::compileAllPipelines()
}
}
VkPipeline CVulkanDevice::pipeline(ShaderType type, uint32_t layerCount, uint32_t ycbcrMask, uint32_t blur_layers, uint32_t colorspace_mask, bool st2084_output, bool force_wide_gammut, bool itm_enable)
VkPipeline CVulkanDevice::pipeline(ShaderType type, uint32_t layerCount, uint32_t ycbcrMask, uint32_t blur_layers, uint32_t colorspace_mask, uint32_t output_eotf, bool itm_enable)
{
std::lock_guard<std::mutex> lock(m_pipelineMutex);
PipelineInfo_t key = {type, layerCount, ycbcrMask, blur_layers, g_uCompositeDebug, colorspace_mask, st2084_output, force_wide_gammut, itm_enable};
PipelineInfo_t key = {type, layerCount, ycbcrMask, blur_layers, g_uCompositeDebug, colorspace_mask, output_eotf, itm_enable};
auto search = m_pipelineMap.find(key);
if (search == m_pipelineMap.end())
{
VkPipeline result = compilePipeline(layerCount, ycbcrMask, type, blur_layers, g_uCompositeDebug, colorspace_mask, st2084_output, force_wide_gammut, itm_enable);
VkPipeline result = compilePipeline(layerCount, ycbcrMask, type, blur_layers, g_uCompositeDebug, colorspace_mask, output_eotf, itm_enable);
m_pipelineMap[key] = result;
return result;
}
@ -1600,6 +1605,17 @@ void CVulkanCmdBuffer::bindTexture(uint32_t slot, std::shared_ptr<CVulkanTexture
m_textureRefs.emplace(texture.get(), texture);
}
void CVulkanCmdBuffer::bindColorMgmtLuts(uint32_t slot, const std::shared_ptr<CVulkanTexture>& lut1d, const std::shared_ptr<CVulkanTexture>& lut3d)
{
m_shaperLut[slot] = lut1d.get();
m_lut3D[slot] = lut3d.get();
if (lut1d != nullptr)
m_textureRefs.emplace(lut1d.get(), lut1d);
if (lut3d != nullptr)
m_textureRefs.emplace(lut3d.get(), lut3d);
}
void CVulkanCmdBuffer::setTextureSrgb(uint32_t slot, bool srgb)
{
m_useSrgb[slot] = srgb;
@ -1660,10 +1676,12 @@ void CVulkanCmdBuffer::dispatch(uint32_t x, uint32_t y, uint32_t z)
VkDescriptorSet descriptorSet = m_device->descriptorSet();
std::array<VkWriteDescriptorSet, 4> writeDescriptorSets;
std::array<VkWriteDescriptorSet, 6> writeDescriptorSets;
std::array<VkDescriptorImageInfo, VKR_SAMPLER_SLOTS> imageDescriptors = {};
std::array<VkDescriptorImageInfo, VKR_SAMPLER_SLOTS> ycbcrImageDescriptors = {};
std::array<VkDescriptorImageInfo, VKR_TARGET_SLOTS> targetDescriptors = {};
std::array<VkDescriptorImageInfo, VKR_LUT3D_COUNT> shaperLutDescriptor = {};
std::array<VkDescriptorImageInfo, VKR_LUT3D_COUNT> lut3DDescriptor = {};
writeDescriptorSets[0] = {
.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET,
@ -1705,6 +1723,26 @@ void CVulkanCmdBuffer::dispatch(uint32_t x, uint32_t y, uint32_t z)
.pImageInfo = ycbcrImageDescriptors.data(),
};
writeDescriptorSets[4] = {
.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET,
.dstSet = descriptorSet,
.dstBinding = 4,
.dstArrayElement = 0,
.descriptorCount = shaperLutDescriptor.size(),
.descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER,
.pImageInfo = shaperLutDescriptor.data(),
};
writeDescriptorSets[5] = {
.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET,
.dstSet = descriptorSet,
.dstBinding = 5,
.dstArrayElement = 0,
.descriptorCount = lut3DDescriptor.size(),
.descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER,
.pImageInfo = lut3DDescriptor.data(),
};
for (uint32_t i = 0; i < VKR_SAMPLER_SLOTS; i++)
{
imageDescriptors[i].sampler = m_device->sampler(m_samplerState[i]);
@ -1721,6 +1759,26 @@ void CVulkanCmdBuffer::dispatch(uint32_t x, uint32_t y, uint32_t z)
imageDescriptors[i].imageView = view;
}
for (uint32_t i = 0; i < VKR_LUT3D_COUNT; i++)
{
SamplerState linearState;
linearState.bNearest = false;
linearState.bUnnormalized = false;
SamplerState nearestState; // TODO(Josh): Probably want to do this when I bring in tetrahedral interpolation.
nearestState.bNearest = true;
nearestState.bUnnormalized = false;
shaperLutDescriptor[i].sampler = m_device->sampler(linearState);
shaperLutDescriptor[i].imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
// TODO(Josh): I hate the fact that srgbView = view *as* raw srgb and treat as linear.
// I need to change this, it's so utterly stupid and confusing.
shaperLutDescriptor[i].imageView = m_shaperLut[i] ? m_shaperLut[i]->srgbView() : nullptr;
lut3DDescriptor[i].sampler = m_device->sampler(linearState);
lut3DDescriptor[i].imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
lut3DDescriptor[i].imageView = m_lut3D[i] ? m_lut3D[i]->srgbView() : nullptr;
}
if (!m_target->isYcbcr())
{
targetDescriptors[0].imageView = m_target->srgbView();
@ -1790,7 +1848,7 @@ void CVulkanCmdBuffer::copyBufferToImage(VkBuffer buffer, VkDeviceSize offset, u
.imageExtent = {
.width = dst->width(),
.height = dst->height(),
.depth = 1,
.depth = dst->depth(),
},
};
@ -1954,7 +2012,18 @@ static VkResult getModifierProps( const VkImageCreateInfo *imageInfo, uint64_t m
return g_device.vk.GetPhysicalDeviceImageFormatProperties2(g_device.physDev(), &imageFormatInfo, &imageProps);
}
bool CVulkanTexture::BInit( uint32_t width, uint32_t height, uint32_t drmFormat, createFlags flags, wlr_dmabuf_attributes *pDMA /* = nullptr */, uint32_t contentWidth /* = 0 */, uint32_t contentHeight /* = 0 */)
static VkImageViewType VulkanImageTypeToViewType(VkImageType type)
{
switch (type)
{
case VK_IMAGE_TYPE_1D: return VK_IMAGE_VIEW_TYPE_1D;
case VK_IMAGE_TYPE_2D: return VK_IMAGE_VIEW_TYPE_2D;
case VK_IMAGE_TYPE_3D: return VK_IMAGE_VIEW_TYPE_3D;
default: abort();
}
}
bool CVulkanTexture::BInit( uint32_t width, uint32_t height, uint32_t depth, uint32_t drmFormat, createFlags flags, wlr_dmabuf_attributes *pDMA /* = nullptr */, uint32_t contentWidth /* = 0 */, uint32_t contentHeight /* = 0 */)
{
VkResult res = VK_ERROR_INITIALIZATION_FAILED;
@ -2012,12 +2081,12 @@ bool CVulkanTexture::BInit( uint32_t width, uint32_t height, uint32_t drmFormat,
VkImageCreateInfo imageInfo = {
.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO,
.imageType = VK_IMAGE_TYPE_2D,
.imageType = flags.imageType,
.format = DRMFormatToVulkan(drmFormat, false),
.extent = {
.width = width,
.height = height,
.depth = 1,
.depth = depth,
},
.mipLevels = 1,
.arrayLayers = 1,
@ -2160,6 +2229,7 @@ bool CVulkanTexture::BInit( uint32_t width, uint32_t height, uint32_t drmFormat,
m_width = width;
m_height = height;
m_depth = depth;
if (contentWidth && contentHeight)
{
@ -2407,7 +2477,7 @@ bool CVulkanTexture::BInit( uint32_t width, uint32_t height, uint32_t drmFormat,
VkImageViewCreateInfo createInfo = {
.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO,
.image = m_vkImage,
.viewType = VK_IMAGE_VIEW_TYPE_2D,
.viewType = VulkanImageTypeToViewType(flags.imageType),
.format = DRMFormatToVulkan(drmFormat, false),
.components = {
.r = VK_COMPONENT_SWIZZLE_IDENTITY,
@ -2492,6 +2562,7 @@ bool CVulkanTexture::BInitFromSwapchain( VkImage image, uint32_t width, uint32_t
m_vkImageMemory = VK_NULL_HANDLE;
m_width = width;
m_height = height;
m_depth = 1;
m_format = format;
m_contentWidth = width;
m_contentHeight = height;
@ -2750,6 +2821,52 @@ void vulkan_present_to_window( void )
vulkan_remake_swapchain();
}
std::shared_ptr<CVulkanTexture> vulkan_create_1d_lut(uint32_t size)
{
CVulkanTexture::createFlags flags;
flags.bSampled = true;
flags.bTransferDst = true;
flags.imageType = VK_IMAGE_TYPE_1D;
auto texture = std::make_shared<CVulkanTexture>();
auto drmFormat = VulkanFormatToDRM( VK_FORMAT_R16G16B16A16_UNORM );
assert( texture->BInit( size, 1u, 1u, drmFormat, flags ) );
return texture;
}
std::shared_ptr<CVulkanTexture> vulkan_create_3d_lut(uint32_t width, uint32_t height, uint32_t depth)
{
CVulkanTexture::createFlags flags;
flags.bSampled = true;
flags.bTransferDst = true;
flags.imageType = VK_IMAGE_TYPE_3D;
auto texture = std::make_shared<CVulkanTexture>();
auto drmFormat = VulkanFormatToDRM( VK_FORMAT_R16G16B16A16_UNORM );
assert( texture->BInit( width, height, depth, drmFormat, flags ) );
return texture;
}
void vulkan_update_luts(const std::shared_ptr<CVulkanTexture>& lut1d, const std::shared_ptr<CVulkanTexture>& lut3d, void* lut1d_data, void* lut3d_data)
{
void* base_dst = g_device.uploadBufferData();
size_t lut1d_size = lut1d->width() * sizeof(uint16_t) * 4;
size_t lut3d_size = lut3d->width() * lut3d->height() * lut3d->depth() * sizeof(uint16_t) * 4;
void* lut1d_dst = base_dst;
void *lut3d_dst = ((uint8_t*)base_dst) + lut1d_size;
memcpy(lut1d_dst, lut1d_data, lut1d_size);
memcpy(lut3d_dst, lut3d_data, lut3d_size);
auto cmdBuffer = g_device.commandBuffer();
cmdBuffer->copyBufferToImage(g_device.uploadBuffer(), 0, 0, lut1d);
cmdBuffer->copyBufferToImage(g_device.uploadBuffer(), lut1d_size, 0, lut3d);
g_device.submit(std::move(cmdBuffer));
}
#if HAVE_OPENVR
std::shared_ptr<CVulkanTexture> vulkan_create_debug_white_texture()
{
@ -2760,7 +2877,7 @@ std::shared_ptr<CVulkanTexture> vulkan_create_debug_white_texture()
flags.bLinear = true;
auto texture = std::make_shared<CVulkanTexture>();
assert( texture->BInit( g_nOutputWidth, g_nOutputHeight, VulkanFormatToDRM( VK_FORMAT_B8G8R8A8_UNORM ), flags ) );
assert( texture->BInit( g_nOutputWidth, g_nOutputHeight, 1u, VulkanFormatToDRM( VK_FORMAT_B8G8R8A8_UNORM ), flags ) );
memset( texture->mappedData(), 0xFF, texture->width() * texture->height() * 4 );
@ -2901,7 +3018,7 @@ static bool vulkan_make_output_images( VulkanOutput_t *pOutput )
VkFormat format = g_bOutputHDREnabled ? pOutput->outputFormatHDR : pOutput->outputFormat;
pOutput->outputImages[0] = std::make_shared<CVulkanTexture>();
bool bSuccess = pOutput->outputImages[0]->BInit( g_nOutputWidth, g_nOutputHeight, VulkanFormatToDRM(format), outputImageflags );
bool bSuccess = pOutput->outputImages[0]->BInit( g_nOutputWidth, g_nOutputHeight, 1u, VulkanFormatToDRM(format), outputImageflags );
if ( bSuccess != true )
{
vk_log.errorf( "failed to allocate buffer for KMS" );
@ -2909,7 +3026,7 @@ static bool vulkan_make_output_images( VulkanOutput_t *pOutput )
}
pOutput->outputImages[1] = std::make_shared<CVulkanTexture>();
bSuccess = pOutput->outputImages[1]->BInit( g_nOutputWidth, g_nOutputHeight, VulkanFormatToDRM(format), outputImageflags );
bSuccess = pOutput->outputImages[1]->BInit( g_nOutputWidth, g_nOutputHeight, 1u, VulkanFormatToDRM(format), outputImageflags );
if ( bSuccess != true )
{
vk_log.errorf( "failed to allocate buffer for KMS" );
@ -3044,7 +3161,7 @@ static void update_tmp_images( uint32_t width, uint32_t height )
createFlags.bStorage = true;
g_output.tmpOutput = std::make_shared<CVulkanTexture>();
bool bSuccess = g_output.tmpOutput->BInit( width, height, DRM_FORMAT_ARGB8888, createFlags, nullptr );
bool bSuccess = g_output.tmpOutput->BInit( width, height, 1u, DRM_FORMAT_ARGB8888, createFlags, nullptr );
if ( !bSuccess )
{
@ -3097,7 +3214,7 @@ std::shared_ptr<CVulkanTexture> vulkan_create_texture_from_dmabuf( struct wlr_dm
//fprintf(stderr, "pDMA->width: %d pDMA->height: %d pDMA->format: 0x%x pDMA->modifier: 0x%lx pDMA->n_planes: %d\n",
// pDMA->width, pDMA->height, pDMA->format, pDMA->modifier, pDMA->n_planes);
if ( pTex->BInit( pDMA->width, pDMA->height, pDMA->format, texCreateFlags, pDMA ) == false )
if ( pTex->BInit( pDMA->width, pDMA->height, 1u, pDMA->format, texCreateFlags, pDMA ) == false )
return nullptr;
return pTex;
@ -3110,7 +3227,7 @@ std::shared_ptr<CVulkanTexture> vulkan_create_texture_from_bits( uint32_t width,
texCreateFlags.bSampled = true;
texCreateFlags.bTransferDst = true;
if ( pTex->BInit( width, height, drmFormat, texCreateFlags, nullptr, contentWidth, contentHeight) == false )
if ( pTex->BInit( width, height, 1u, drmFormat, texCreateFlags, nullptr, contentWidth, contentHeight) == false )
return nullptr;
memcpy( g_device.uploadBufferData(), bits, width * height * DRMFormatGetBPP(drmFormat) );
@ -3153,7 +3270,7 @@ std::shared_ptr<CVulkanTexture> vulkan_acquire_screenshot_texture(uint32_t width
screenshotImageFlags.bStorage = true;
}
bool bSuccess = pScreenshotImage->BInit( width, height, drmFormat, screenshotImageFlags );
bool bSuccess = pScreenshotImage->BInit( width, height, 1u, drmFormat, screenshotImageFlags );
pScreenshotImage->setStreamColorspace(colorspace);
assert( bSuccess );
@ -3354,6 +3471,9 @@ bool vulkan_composite( const struct FrameInfo_t *frameInfo, std::shared_ptr<CVul
auto cmdBuffer = g_device.commandBuffer();
for (uint32_t i = 0; i < EOTF_Count; i++)
cmdBuffer->bindColorMgmtLuts(i, frameInfo->shaperLut[i], frameInfo->lut3D[i]);
if ( frameInfo->useFSRLayer0 )
{
uint32_t inputX = frameInfo->layers[0].tex->width();
@ -3443,7 +3563,7 @@ bool vulkan_composite( const struct FrameInfo_t *frameInfo, std::shared_ptr<CVul
if (frameInfo->layerCount >= 2 && frameInfo->layers[1].zpos == g_zposOverride)
blur_layer_count++;
cmdBuffer->bindPipeline(g_device.pipeline(type, blur_layer_count, frameInfo->ycbcrMask() & 0x3u, 0, frameInfo->colorspaceMask(), g_bOutputHDREnabled, false, false));
cmdBuffer->bindPipeline(g_device.pipeline(type, blur_layer_count, frameInfo->ycbcrMask() & 0x3u, 0, frameInfo->colorspaceMask(), g_ColorMgmt.current.outputEncodingEOTF ));
cmdBuffer->bindTarget(g_output.tmpOutput);
for (uint32_t i = 0; i < blur_layer_count; i++)
{
@ -3459,7 +3579,7 @@ bool vulkan_composite( const struct FrameInfo_t *frameInfo, std::shared_ptr<CVul
cmdBuffer->dispatch(div_roundup(currentOutputWidth, pixelsPerGroup), div_roundup(currentOutputHeight, pixelsPerGroup));
type = frameInfo->blurLayer0 == BLUR_MODE_COND ? SHADER_TYPE_BLUR_COND : SHADER_TYPE_BLUR;
cmdBuffer->bindPipeline(g_device.pipeline(type, frameInfo->layerCount, frameInfo->ycbcrMask(), blur_layer_count, frameInfo->colorspaceMask(), g_bOutputHDREnabled, false, false));
cmdBuffer->bindPipeline(g_device.pipeline(type, frameInfo->layerCount, frameInfo->ycbcrMask(), blur_layer_count, frameInfo->colorspaceMask(), g_ColorMgmt.current.outputEncodingEOTF ));
bind_all_layers(cmdBuffer.get(), frameInfo);
cmdBuffer->bindTarget(compositeImage);
cmdBuffer->bindTexture(VKR_BLUR_EXTRA_SLOT, g_output.tmpOutput);
@ -3471,7 +3591,7 @@ bool vulkan_composite( const struct FrameInfo_t *frameInfo, std::shared_ptr<CVul
}
else
{
cmdBuffer->bindPipeline( g_device.pipeline(SHADER_TYPE_BLIT, frameInfo->layerCount, frameInfo->ycbcrMask(), 0u, frameInfo->colorspaceMask(), g_bOutputHDREnabled, false, g_bOutputHDREnabled ? g_bHDRItmEnable : false));
cmdBuffer->bindPipeline( g_device.pipeline(SHADER_TYPE_BLIT, frameInfo->layerCount, frameInfo->ycbcrMask(), 0u, frameInfo->colorspaceMask(), g_ColorMgmt.current.outputEncodingEOTF ));
bind_all_layers(cmdBuffer.get(), frameInfo);
cmdBuffer->bindTarget(compositeImage);
cmdBuffer->pushConstants<BlitPushData_t>(frameInfo);
@ -3729,7 +3849,7 @@ std::shared_ptr<CVulkanTexture> vulkan_create_texture_from_wlr_buffer( struct wl
CVulkanTexture::createFlags texCreateFlags;
texCreateFlags.bSampled = true;
texCreateFlags.bTransferDst = true;
if ( pTex->BInit( width, height, drmFormat, texCreateFlags ) == false )
if ( pTex->BInit( width, height, 1u, drmFormat, texCreateFlags ) == false )
return nullptr;
auto cmdBuffer = g_device.commandBuffer();

View file

@ -129,6 +129,7 @@ public:
bLinear = false;
bExportable = false;
bSwapchain = false;
imageType = VK_IMAGE_TYPE_2D;
}
bool bFlippable : 1;
@ -140,9 +141,10 @@ public:
bool bLinear : 1;
bool bExportable : 1;
bool bSwapchain : 1;
VkImageType imageType;
};
bool BInit( uint32_t width, uint32_t height, uint32_t drmFormat, createFlags flags, wlr_dmabuf_attributes *pDMA = nullptr, uint32_t contentWidth = 0, uint32_t contentHeight = 0 );
bool BInit( uint32_t width, uint32_t height, uint32_t depth, uint32_t drmFormat, createFlags flags, wlr_dmabuf_attributes *pDMA = nullptr, uint32_t contentWidth = 0, uint32_t contentHeight = 0 );
bool BInitFromSwapchain( VkImage image, uint32_t width, uint32_t height, VkFormat format );
inline VkImageView view( bool linear ) { return linear ? m_linearView : m_srgbView; }
@ -152,6 +154,7 @@ public:
inline VkImageView chromaView() { return m_chromaView; }
inline uint32_t width() { return m_width; }
inline uint32_t height() { return m_height; }
inline uint32_t depth() { return m_depth; }
inline uint32_t contentWidth() {return m_contentWidth; }
inline uint32_t contentHeight() {return m_contentHeight; }
inline uint32_t rowPitch() { return m_unRowPitch; }
@ -198,6 +201,7 @@ private:
uint32_t m_width = 0;
uint32_t m_height = 0;
uint32_t m_depth = 0;
uint32_t m_contentWidth = 0;
uint32_t m_contentHeight = 0;
@ -233,6 +237,8 @@ struct FrameInfo_t
BlurMode blurLayer0;
int blurRadius;
std::shared_ptr<CVulkanTexture> shaperLut[EOTF_Count];
std::shared_ptr<CVulkanTexture> lut3D[EOTF_Count];
int layerCount;
struct Layer_t
@ -333,6 +339,10 @@ bool acquire_next_image( void );
bool vulkan_primary_dev_id(dev_t *id);
bool vulkan_supports_modifiers(void);
std::shared_ptr<CVulkanTexture> vulkan_create_1d_lut(uint32_t size);
std::shared_ptr<CVulkanTexture> vulkan_create_3d_lut(uint32_t width, uint32_t height, uint32_t depth);
void vulkan_update_luts(const std::shared_ptr<CVulkanTexture>& lut1d, const std::shared_ptr<CVulkanTexture>& lut3d, void* lut1d_data, void* lut3d_data);
struct wlr_renderer *vulkan_renderer_create( void );
using mat3x4 = std::array<std::array<float, 4>, 3>;
@ -365,6 +375,9 @@ struct gamescope_color_mgmt_luts
std::vector<uint16_t> lut3d;
std::vector<uint16_t> lut1d;
std::shared_ptr<CVulkanTexture> vk_lut3d;
std::shared_ptr<CVulkanTexture> vk_lut1d;
void reset()
{
lut3d.clear();

View file

@ -493,24 +493,22 @@ vec4 gaussian_blur(sampler2D layerSampler, uint layerIdx, vec2 pos, uint radius,
vec4 tmp0 = textureCond(layerSampler, layerIdx, pos - posOffset, unnormalized) * weights[i];
if (vertical)
{
if (c_st2084Output)
tmp0.rgb = pqToNits(tmp0.rgb);
tmp0.rgb = colorspace_blend_tf(tmp0.rgb, c_output_eotf);
}
else
{
tmp0 = convert_to_dst_colorspace(tmp0, get_layer_colorspace(layerIdx));
tmp0.rgb = colorspace_output_tf(tmp0.rgb, get_layer_colorspace(layerIdx));
}
color += tmp0;
vec4 tmp1 = textureCond(layerSampler, layerIdx, pos + posOffset, unnormalized) * weights[i];
if (vertical)
{
if (c_st2084Output)
tmp1.rgb = pqToNits(tmp1.rgb);
tmp1.rgb = colorspace_blend_tf(tmp1.rgb, c_output_eotf);
}
else
{
tmp1 = convert_to_dst_colorspace(tmp1, get_layer_colorspace(layerIdx));
tmp1.rgb = colorspace_output_tf(tmp1.rgb, get_layer_colorspace(layerIdx));
}
color += tmp1;
}

View file

@ -61,6 +61,18 @@ vec3 pqToNits(vec3 pq) {
return 10000.0 * pow(num / den, vec3(oo_m1));
}
// does NOT change primaries, just
// the pq value in nits / 80.0f!
vec3 pqToScRGBEncoding(vec3 pq)
{
return pqToNits(pq) / 80.0f;
}
vec3 scRGBEncodingToPQ(vec3 scRGBEncodedValue)
{
return pqToNits(scRGBEncodedValue * 80.0f);
}
// This is apparently defined at 80 nits...
// May want to take liberties with this when displaying
// on SDR though... 100 may be a better fit for most content
@ -134,39 +146,6 @@ vec3 convert_primaries(vec3 color, mat3 src_to_xyz, mat3 xyz_to_dst) {
return color * mat3(src_to_xyz * xyz_to_dst);
}
const float A = 0.15f;
const float B = 0.50f;
const float C = 0.10f;
const float D = 0.20f;
const float E = 0.02f;
const float F = 0.30f;
const float W = 11.2f;
vec3 Uncharted2Tonemap(vec3 x) {
return ((x * (A * x + C * B) + D * E) / (x * (A * x + B) + D * F)) - E / F;
}
float Uncharted2Tonemap(float x) {
return ((x * (A * x + C * B) + D * E) / (x * (A * x + B) + D * F)) - E / F;
}
vec3 tonemap_filmic(vec3 color) {
vec3 curr = Uncharted2Tonemap(color);
float white_scale = 1.0 / Uncharted2Tonemap(W);
return curr * white_scale;
}
vec3 tonemap_reinhard(vec3 color) {
color = color / (1.0 + color);
return color;
}
vec3 tonemap(vec3 color) {
if (checkDebugFlag(compositedebug_Tonemap_Reinhard))
return tonemap_reinhard(color);
return tonemap_filmic(color);
}
// Rep. ITU-R BT.2446-1 Table 2-4 (inversed)
// BT.2446 Method A inverse tone mapping (itm)
vec3 bt2446a_inverse_tonemapping(
@ -327,70 +306,117 @@ vec3 bt2446a_inverse_tonemapping(
// Generic helper
vec4 convert_to_dst_colorspace(vec4 color, uint colorspace)
{
if (colorspace == colorspace_pq) {
color.rgb = pqToNits(color.rgb);
vec3 colorspace_plane_degamma_tf(vec3 color, uint colorspace) {
// matches with colorspace_to_plane_degamma_tf in drm.cpp
if (checkDebugFlag(compositedebug_Heatmap)) {
color.rgb = hdr_heatmap(color.rgb, true, true, c_st2084Output);
} else {
if (!c_st2084Output) {
// HDR10 ST2084 is rec2020.
color.rgb = convert_primaries(color.rgb, rec2020_to_xyz, xyz_to_rec709);
color.rgb = nitsToLinear(color.rgb);
color.rgb = tonemap(color.rgb);
}
}
} else if (colorspace == colorspace_scRGB) {
color.rgb = scrgbToNits(color.rgb);
switch (colorspace) {
default: return vec3(1, 1, 0); // should never happen
if (checkDebugFlag(compositedebug_Heatmap)) {
color.rgb = hdr_heatmap(color.rgb, false, true, c_st2084Output);
} else {
if (!c_st2084Output) {
// scRGB is rec709.
color.rgb = nitsToLinear(color.rgb);
color.rgb = tonemap(color.rgb);
} else {
// scRGB is rec709.
// ST2084 output needs rec2020.
color.rgb = convert_primaries(color.rgb, rec709_to_xyz, xyz_to_rec2020);
}
}
} else if (colorspace == colorspace_sRGB) {
color.rgb = srgbToLinear(color.rgb);
if(c_itmEnable) {
if (!c_forceWideGammut)
color.rgb = convert_primaries(color.rgb, rec709_to_xyz, xyz_to_rec2020);
color.rgb = bt2446a_inverse_tonemapping(color.rgb, u_itmSdrNits, u_itmTargetNits);
}
if (checkDebugFlag(compositedebug_Heatmap)) {
color.rgb = hdr_heatmap(color.rgb, c_itmEnable, c_itmEnable, c_st2084Output);
} else {
if (!c_itmEnable && c_st2084Output) {
color.rgb = linearToNits(color.rgb);
if (!c_forceWideGammut)
color.rgb = convert_primaries(color.rgb, rec709_to_xyz, xyz_to_rec2020);
}
}
} else if (colorspace == colorspace_linear) {
if(c_itmEnable) {
if (!c_forceWideGammut)
color.rgb = convert_primaries(color.rgb, rec709_to_xyz, xyz_to_rec2020);
color.rgb = bt2446a_inverse_tonemapping(color.rgb, u_itmSdrNits, u_itmTargetNits);
}
if (checkDebugFlag(compositedebug_Heatmap)) {
color.rgb = hdr_heatmap(color.rgb, c_itmEnable, c_itmEnable, c_st2084Output);
} else {
if (!c_itmEnable && c_st2084Output) {
color.rgb = linearToNits(color.rgb);
if (!c_forceWideGammut)
color.rgb = convert_primaries(color.rgb, rec709_to_xyz, xyz_to_rec2020);
}
}
case colorspace_linear: // Using sRGB image view. Unlike DRM which doesn't get that liberty for scanout.
case colorspace_scRGB:
return color;
case colorspace_sRGB:
return srgbToLinear(color);
case colorspace_pq:
return pqToScRGBEncoding(color);
}
return color;
}
vec3 colorspace_plane_shaper_tf(vec3 color, uint colorspace) {
// matches with colorspace_to_plane_regamma_tf in drm.cpp
switch (colorspace) {
default: return vec3(0, 1, 1); // should never happen
case colorspace_linear:
case colorspace_sRGB:
return linearToSrgb(color);
case colorspace_scRGB:
case colorspace_pq:
return scRGBEncodingToPQ(color);
}
}
// pre-blend doing display EOTF -> display linearized
vec3 colorspace_blend_tf(vec3 color, uint eotf) {
switch (eotf) {
default: return vec3(1, 0, 0); // should never happen
// Note from Josh:
//
// We are kinda halfway between output space and not at this point
// the color primaries, gamut remapping has already been performed
// in display output 2.2 space, but that doesn't change the fact
// that we haven't displayed it yet!
//
// Perform the alpha blending with sRGB linearization (like the CONTENT specifies) here
// the primaries and gamut remapping transformations we performed in output 2.2 space do NOT matter.
// This is more correct than using gamma 2.2 for that here.
case EOTF_Gamma22:
return srgbToLinear(color);
case EOTF_PQ:
return pqToScRGBEncoding(color);
}
}
// post blend doing display linearized -> display EOTF
vec3 colorspace_output_tf(vec3 color, uint eotf) {
switch (eotf) {
default: return vec3(0, 1, 0); // should never happen
// see comment in colorspace_blend_tf
case EOTF_Gamma22:
return linearToSrgb(color);
case EOTF_PQ:
return scRGBEncodingToPQ(color);
}
}
// matches how we treat content here :)
uint colorspace_to_eotf(uint colorspace)
{
// matches with ColorSpaceToEOTFIndex in drm.cpp
switch ( colorspace )
{
default:
case colorspace_linear: // Not actually linear, just Linear vs sRGB image views in Vulkan. Still viewed as sRGB on the DRM side.
case colorspace_sRGB:
// SDR sRGB content treated as native Gamma 22 curve. No need to do sRGB -> 2.2 or whatever.
return EOTF_Gamma22;
case colorspace_scRGB:
// Okay, so this is WEIRD right? OKAY Let me explain it to you.
// The plan for scRGB content is to go from scRGB -> PQ in a SHAPER_TF
// before indexing into the shaper.
return EOTF_PQ;
case colorspace_pq:
return EOTF_PQ;
}
}
float half_texel_scale(float x, float half_texel)
{
return mix(0.0f + half_texel, 1.0f - half_texel, x);
}
vec3 half_texel_scale(vec3 x, vec3 half_texel)
{
return mix(vec3(0.0f) + half_texel, vec3(1.0f) - half_texel, x);
}
vec3 perform_1dlut(vec3 color, sampler1D shaperLUT) {
int size = textureSize(shaperLUT, 0);
float offset = 0.5f / float(size);
return vec3(
textureLod(shaperLUT, half_texel_scale(color.r, offset), 0.0f).r,
textureLod(shaperLUT, half_texel_scale(color.g, offset), 0.0f).g,
textureLod(shaperLUT, half_texel_scale(color.b, offset), 0.0f).b);
}
vec3 perform_3dlut(vec3 color, sampler3D lut3D) {
ivec3 size = textureSize(lut3D, 0);
vec3 offset = 0.5f / vec3(float(size.x), float(size.y), float(size.z));
return textureLod(lut3D, half_texel_scale(color.rgb, offset), 0.0f).rgb;
}

View file

@ -24,6 +24,45 @@ void compositing_debug(uvec2 coord) {
}
}
vec3 layerColorFromColorspaceToScRGB(vec3 color, uint layerIdx) {
uint colorspace = get_layer_colorspace(layerIdx);
color = colorspace_plane_degamma_tf(color, colorspace);
if (c_itm_enable)
{
color = bt2446a_inverse_tonemapping(color, u_itmSdrNits, u_itmTargetNits);
colorspace = colorspace_pq;
}
if (checkDebugFlag(compositedebug_Heatmap))
{
// Debug HDR heatmap.
color = hdr_heatmap(color, colorspace);
}
else
{
// Shaper + 3D LUT path to match DRM.
uint plane_eotf = colorspace_to_eotf(colorspace);
color = colorspace_plane_shaper_tf(color, colorspace);
// The shaper TF is basically just a regamma to get into something the shaper LUT can handle.
//
// Despite naming, degamma + shaper TF are NOT necessarily the inverse of each other. ^^^
// This gets the color ready to go into the shaper LUT.
// ie. scRGB -> PQ
//
// We also need to do degamma here for non-linear views to blend in linear space.
// ie. PQ -> PQ would need us to manually do bilinear here.
color = perform_1dlut(color, s_shaperLut[plane_eotf]);
color = perform_3dlut(color, s_lut3D[plane_eotf]);
color = colorspace_blend_tf(color, c_output_eotf);
}
return color;
}
vec4 sampleLayer(sampler2D layerSampler, uint layerIdx, vec2 uv, bool unnormalized) {
vec2 coord = ((uv + u_offset[layerIdx]) * u_scale[layerIdx]);
vec2 texSize = textureSize(layerSampler, 0);
@ -43,15 +82,14 @@ vec4 sampleLayer(sampler2D layerSampler, uint layerIdx, vec2 uv, bool unnormaliz
vec4 color = textureLod(layerSampler, coord, 0.0f);
return convert_to_dst_colorspace(color, get_layer_colorspace(layerIdx));
// TODO(Josh): If colorspace != linear, emulate bilinear ourselves to blend
// in linear space!
// Split this into two parts!
color.rgb = layerColorFromColorspaceToScRGB(color.rgb, layerIdx);
return color;
}
vec3 encodeOutputColor(vec3 linearOrNits) {
if (!c_st2084Output) {
// linearOrNits -> linear
return linearToSrgb(linearOrNits.rgb);
} else {
// linearOrNits -> nits
return nitsToPq(linearOrNits.rgb);
}
vec3 encodeOutputColor(vec3 value) {
return colorspace_output_tf(value, c_output_eotf);
}

View file

@ -59,18 +59,13 @@ void rcasComposite(uvec2 pos)
if (all(lessThan(rcasPos, layer0Extent))) {
FsrRcasF(outputValue.r, outputValue.g, outputValue.b, rcasPos, u_c1.xxxx);
if (c_layerCount == 1) {
// Technically the wrong color space if you just have one layer.
// but doing srgb -> linear -> srgb just for scaling is silly. This is good enough.
outputValue *= u_opacity[0];
}
outputValue.rgb = layerColorFromColorspaceToScRGB(outputValue.rgb, 0);
outputValue *= u_opacity[0];
}
}
if (c_layerCount > 1) {
outputValue = srgbToLinear(outputValue);
outputValue *= u_opacity[0];
vec2 uv = vec2(pos);
for (int i = 1; i < c_layerCount; i++) {
@ -79,10 +74,9 @@ void rcasComposite(uvec2 pos)
float layerAlpha = opacity * layerColor.a;
outputValue = layerColor.rgb * opacity + outputValue * (1.0f - layerAlpha);
}
outputValue = linearToSrgb(outputValue);
}
outputValue = encodeOutputColor(outputValue);
imageStore(dst, ivec2(pos), vec4(outputValue, 0));
if (checkDebugFlag(compositedebug_Markers))

View file

@ -6,9 +6,8 @@ layout(constant_id = 2) const uint c_compositing_debug = 0;
layout(constant_id = 3) const int c_blur_layer_count = 0;
layout(constant_id = 4) const uint c_colorspaceMask = 0;
layout(constant_id = 5) const bool c_st2084Output = false;
layout(constant_id = 6) const bool c_forceWideGammut = false;
layout(constant_id = 7) const bool c_itmEnable = false;
layout(constant_id = 5) const uint c_output_eotf = 0;
layout(constant_id = 7) const bool c_itm_enable = false;
const int colorspace_linear = 0;
const int colorspace_sRGB = 1;
@ -17,6 +16,10 @@ const int colorspace_pq = 3;
const int colorspace_reserved = 3;
const int colorspace_max_bits = 2;
const int EOTF_Gamma22 = 0;
const int EOTF_PQ = 1;
const int EOTF_Count = 2;
const uint compositedebug_Markers = 1u << 0;
const uint compositedebug_PlaneBorders = 1u << 1;
const uint compositedebug_Heatmap = 1u << 2;
@ -38,3 +41,6 @@ layout(binding = 1, rgba8) writeonly uniform image2D dst_chroma;
layout(binding = 2) uniform sampler2D s_samplers[VKR_SAMPLER_SLOTS];
layout(binding = 3) uniform sampler2D s_ycbcr_samplers[VKR_SAMPLER_SLOTS];
layout(binding = 4) uniform sampler1D s_shaperLut[VKR_LUT3D_COUNT];
layout(binding = 5) uniform sampler3D s_lut3D[VKR_LUT3D_COUNT];

View file

@ -9,4 +9,6 @@
#define VKR_NIS_COEF_SCALER_SLOT (VKR_BLUR_EXTRA_SLOT + 1u)
#define VKR_NIS_COEF_USM_SLOT (VKR_NIS_COEF_SCALER_SLOT + 1u)
#define VKR_LUT3D_COUNT 2 // Must match EOTF_Count
#endif

View file

@ -159,15 +159,19 @@ vec3 hdr_heatmap_lilium_impl(float Y) {
return outColor;
}
vec3 hdr_heatmap(vec3 inputColor, bool in_2020, bool in_nits, bool out_nits) {
bool colorspace_is_2020(uint colorspace)
{
return colorspace == colorspace_pq;
}
vec3 hdr_heatmap(vec3 inputColor, uint colorspace)
{
vec3 xyz;
// If the input is SDR, then just multiply by 100 (typical sRGB mastering)
// don't multiply by our typical SDR scale.
if (!in_nits)
inputColor *= 100.0f;
// scRGB encoding (linear / 80.0f) -> nits;
inputColor *= 80.0f;
if (in_2020)
if (colorspace_is_2020(colorspace))
xyz = inputColor * rec2020_to_xyz;
else
xyz = inputColor * rec709_to_xyz;
@ -178,11 +182,10 @@ vec3 hdr_heatmap(vec3 inputColor, bool in_2020, bool in_nits, bool out_nits) {
else
outputColor = hdr_heatmap_lilium_impl(xyz.y); // Lilium Heatmap
if (c_st2084Output)
outputColor = convert_primaries(outputColor, rec709_to_xyz, xyz_to_rec2020);
if (out_nits)
outputColor *= u_linearToNits;
// Brighten the heatmap up to something reasonable.
// If we care enough we could use some setting here someday.
if (c_output_eotf == EOTF_PQ)
outputColor *= 400.0f / 80.0f; // 400 nit as scRGB TF for blend space.
return outputColor;
}

View file

@ -142,16 +142,22 @@ update_color_mgmt()
const displaycolorimetry_t& displayColorimetry = g_ColorMgmt.pending.displayColorimetry;
const displaycolorimetry_t& outputEncodingColorimetry = g_ColorMgmt.pending.outputEncodingColorimetry;
std::vector<uint16_t> lut3d;
uint32_t nLutEdgeSize3d = 17;
lut3d.resize( nLutEdgeSize3d*nLutEdgeSize3d*nLutEdgeSize3d*4 );
std::vector<uint16_t> lut1d;
uint32_t nLutSize1d = 4096;
lut1d.resize( nLutSize1d*4 );
for ( uint32_t nInputEOTF = 0; nInputEOTF < EOTF_Count; nInputEOTF++ )
{
std::vector<uint16_t> lut3d;
uint32_t nLutEdgeSize3d = 17;
lut3d.resize( nLutEdgeSize3d*nLutEdgeSize3d*nLutEdgeSize3d*4 );
std::vector<uint16_t> lut1d;
uint32_t nLutSize1d = 4096;
lut1d.resize( nLutSize1d*4 );
if (!g_ColorMgmtLuts[nInputEOTF].vk_lut1d)
g_ColorMgmtLuts[nInputEOTF].vk_lut1d = vulkan_create_1d_lut(4096);
if (!g_ColorMgmtLuts[nInputEOTF].vk_lut3d)
g_ColorMgmtLuts[nInputEOTF].vk_lut3d = vulkan_create_3d_lut(17, 17, 17);
displaycolorimetry_t inputColorimetry{};
colormapping_t colorMapping{};
@ -217,12 +223,16 @@ update_color_mgmt()
}
else if ( !lut3d.empty() && !lut1d.empty() )
{
g_ColorMgmtLuts[nInputEOTF] = gamescope_color_mgmt_luts{ lut3d, lut1d };
g_ColorMgmtLuts[nInputEOTF].lut3d = std::move(lut3d);
g_ColorMgmtLuts[nInputEOTF].lut1d = std::move(lut1d);
}
else
{
g_ColorMgmtLuts[nInputEOTF].reset();
}
if (!g_ColorMgmtLuts[nInputEOTF].lut3d.empty() && !g_ColorMgmtLuts[nInputEOTF].lut1d.empty())
vulkan_update_luts(g_ColorMgmtLuts[nInputEOTF].vk_lut1d, g_ColorMgmtLuts[nInputEOTF].vk_lut3d, g_ColorMgmtLuts[nInputEOTF].lut1d.data(), g_ColorMgmtLuts[nInputEOTF].lut3d.data());
}
}
else
@ -2225,12 +2235,10 @@ paint_all(bool async)
bNeedsComposite |= bNeedsNearest;
bNeedsComposite |= bDrewCursor;
// Disable FSR if outputting HDR or not SDR colorspace game.
// We can fix the former at some point, but the latter is much harder.
if ( g_bOutputHDREnabled || !( frameInfo.layers[0].colorspace == GAMESCOPE_APP_TEXTURE_COLORSPACE_SRGB || frameInfo.layers[0].colorspace == GAMESCOPE_APP_TEXTURE_COLORSPACE_LINEAR ) )
for (uint32_t i = 0; i < EOTF_Count; i++)
{
frameInfo.useFSRLayer0 = false;
frameInfo.useNISLayer0 = false;
frameInfo.shaperLut[i] = g_ColorMgmtLuts[i].vk_lut1d;
frameInfo.lut3D[i] = g_ColorMgmtLuts[i].vk_lut3d;
}
if ( !BIsNested() && g_bOutputHDREnabled )