diff --git a/intern/ghost/intern/GHOST_ContextCGL.mm b/intern/ghost/intern/GHOST_ContextCGL.mm index dba4d46ca8b..671038f6684 100644 --- a/intern/ghost/intern/GHOST_ContextCGL.mm +++ b/intern/ghost/intern/GHOST_ContextCGL.mm @@ -25,6 +25,8 @@ #include #include +static const MTLPixelFormat METAL_FRAMEBUFFERPIXEL_FORMAT_EDR = MTLPixelFormatRGBA16Float; + static void ghost_fatal_error_dialog(const char *msg) { @autoreleasepool { @@ -85,6 +87,19 @@ GHOST_ContextCGL::GHOST_ContextCGL(bool stereoVisual, [m_metalLayer removeAllAnimations]; [m_metalLayer setDevice:metalDevice]; m_metalLayer.allowsNextDrawableTimeout = NO; + + /* Enable EDR support. This is done by: + * 1. Using a floating point render target, so that values ouside 0..1 can be used + * 2. Informing the OS that we are EDR aware, and intend to use values outside 0..1 + * 3. Setting the extended sRGB color space so that the OS knows how to interpret the + * values. */ + m_metalLayer.wantsExtendedDynamicRangeContent = YES; + m_metalLayer.pixelFormat = METAL_FRAMEBUFFERPIXEL_FORMAT_EDR; + const CFStringRef name = kCGColorSpaceExtendedSRGB; + CGColorSpaceRef colorspace = CGColorSpaceCreateWithName(name); + m_metalLayer.colorspace = colorspace; + CGColorSpaceRelease(colorspace); + metalInit(); } else { @@ -199,8 +214,6 @@ GHOST_TSuccess GHOST_ContextCGL::releaseNativeHandles() return GHOST_kSuccess; } -static const MTLPixelFormat METAL_FRAMEBUFFERPIXEL_FORMAT = MTLPixelFormatBGRA8Unorm; - void GHOST_ContextCGL::metalInit() { @autoreleasepool { @@ -266,11 +279,12 @@ void GHOST_ContextCGL::metalInit() desc.fragmentFunction = [library newFunctionWithName:@"fragment_shader"]; desc.vertexFunction = [library newFunctionWithName:@"vertex_shader"]; + [desc.colorAttachments objectAtIndexedSubscript:0].pixelFormat = + METAL_FRAMEBUFFERPIXEL_FORMAT_EDR; + /* Ensure library is released. */ [library autorelease]; - [desc.colorAttachments objectAtIndexedSubscript:0].pixelFormat = METAL_FRAMEBUFFERPIXEL_FORMAT; - m_metalRenderPipeline = (MTLRenderPipelineState *)[device newRenderPipelineStateWithDescriptor:desc error:&error]; @@ -332,7 +346,7 @@ void GHOST_ContextCGL::metalUpdateFramebuffer() id device = m_metalLayer.device; MTLTextureDescriptor *overlayDesc = [MTLTextureDescriptor - texture2DDescriptorWithPixelFormat:MTLPixelFormatRGBA16Float + texture2DDescriptorWithPixelFormat:METAL_FRAMEBUFFERPIXEL_FORMAT_EDR width:width height:height mipmapped:NO]; @@ -347,12 +361,9 @@ void GHOST_ContextCGL::metalUpdateFramebuffer() else { overlayTex.label = [NSString stringWithFormat:@"Metal Overlay for GHOST Context %p", this]; //@""; - - // NSLog(@"Created new Metal Overlay (backbuffer) for context %p\n", this); } - m_defaultFramebufferMetalTexture[current_swapchain_index].texture = - overlayTex; //[(MTLTexture *)overlayTex retain]; + m_defaultFramebufferMetalTexture[current_swapchain_index].texture = overlayTex; /* Clear texture on create */ id cmdBuffer = [s_sharedMetalCommandQueue commandBuffer]; diff --git a/intern/ghost/intern/GHOST_WindowCocoa.mm b/intern/ghost/intern/GHOST_WindowCocoa.mm index e01faa17dfa..608181987c9 100644 --- a/intern/ghost/intern/GHOST_WindowCocoa.mm +++ b/intern/ghost/intern/GHOST_WindowCocoa.mm @@ -347,6 +347,20 @@ GHOST_WindowCocoa::GHOST_WindowCocoa(GHOST_SystemCocoa *systemCocoa, [m_metalLayer removeAllAnimations]; [m_metalLayer setDevice:metalDevice]; + if (type == GHOST_kDrawingContextTypeMetal) { + /* Enable EDR support. This is done by: + * 1. Using a floating point render target, so that values ouside 0..1 can be used + * 2. Informing the OS that we are EDR aware, and intend to use values outside 0..1 + * 3. Setting the extended sRGB color space so that the OS knows how to interpret the + * values. */ + m_metalLayer.wantsExtendedDynamicRangeContent = YES; + m_metalLayer.pixelFormat = MTLPixelFormatRGBA16Float; + const CFStringRef name = kCGColorSpaceExtendedSRGB; + CGColorSpaceRef colorspace = CGColorSpaceCreateWithName(name); + m_metalLayer.colorspace = colorspace; + CGColorSpaceRelease(colorspace); + } + m_metalView = [[CocoaMetalView alloc] initWithFrame:rect]; [m_metalView setWantsLayer:YES]; [m_metalView setLayer:m_metalLayer]; diff --git a/intern/opencolorio/gpu_shader_display_transform_frag.glsl b/intern/opencolorio/gpu_shader_display_transform_frag.glsl index 92cf9b013fd..1d9a3f69dde 100644 --- a/intern/opencolorio/gpu_shader_display_transform_frag.glsl +++ b/intern/opencolorio/gpu_shader_display_transform_frag.glsl @@ -169,7 +169,16 @@ vec4 OCIO_ProcessColor(vec4 col, vec4 col_overlay) * merge UI using alpha blending in the correct color space. */ if (parameters.use_overlay) { col.rgb = pow(col.rgb, vec3(parameters.exponent * 2.2)); - col = clamp(col, 0.0, 1.0); + + if (!parameters.use_hdr) { + /* If we're not using an extended colour space, clamp the color 0..1. */ + col = clamp(col, 0.0, 1.0); + } + else { + /* When using extended colorspace, interpolate towards clamped color to improve display of + * alpha-blended overlays. */ + col = mix(max(col, 0.0), clamp(col, 0.0, 1.0), col_overlay.a); + } col *= 1.0 - col_overlay.a; col += col_overlay; /* Assumed unassociated alpha. */ col.rgb = pow(col.rgb, vec3(1.0 / 2.2)); diff --git a/intern/opencolorio/ocio_capi.cc b/intern/opencolorio/ocio_capi.cc index 46a8287dbb7..33ea7be7dd1 100644 --- a/intern/opencolorio/ocio_capi.cc +++ b/intern/opencolorio/ocio_capi.cc @@ -290,7 +290,8 @@ bool OCIO_gpuDisplayShaderBind(OCIO_ConstConfigRcPtr *config, const float exponent, const float dither, const bool use_predivide, - const bool use_overlay) + const bool use_overlay, + const bool use_hdr) { return impl->gpuDisplayShaderBind(config, input, @@ -302,7 +303,8 @@ bool OCIO_gpuDisplayShaderBind(OCIO_ConstConfigRcPtr *config, exponent, dither, use_predivide, - use_overlay); + use_overlay, + use_hdr); } void OCIO_gpuDisplayShaderUnbind() diff --git a/intern/opencolorio/ocio_capi.h b/intern/opencolorio/ocio_capi.h index 76bd92f025f..6097189cb7c 100644 --- a/intern/opencolorio/ocio_capi.h +++ b/intern/opencolorio/ocio_capi.h @@ -197,7 +197,8 @@ bool OCIO_gpuDisplayShaderBind(OCIO_ConstConfigRcPtr *config, const float exponent, const float dither, const bool use_predivide, - const bool use_overlay); + const bool use_overlay, + const bool use_hdr); void OCIO_gpuDisplayShaderUnbind(void); void OCIO_gpuCacheFree(void); diff --git a/intern/opencolorio/ocio_impl.h b/intern/opencolorio/ocio_impl.h index 64f0b1c4f09..4f334c0344d 100644 --- a/intern/opencolorio/ocio_impl.h +++ b/intern/opencolorio/ocio_impl.h @@ -113,7 +113,8 @@ class IOCIOImpl { const float /*exponent*/, const float /*dither*/, const bool /*use_predivide*/, - const bool /*use_overlay*/) + const bool /*use_overlay*/, + const bool /*use_hdr*/) { return false; } @@ -310,7 +311,8 @@ class OCIOImpl : public IOCIOImpl { const float exponent, const float dither, const bool use_predivide, - const bool use_overlay); + const bool use_overlay, + const bool use_hdr); void gpuDisplayShaderUnbind(void); void gpuCacheFree(void); diff --git a/intern/opencolorio/ocio_impl_glsl.cc b/intern/opencolorio/ocio_impl_glsl.cc index 6947690dc22..2f0f9d72eb6 100644 --- a/intern/opencolorio/ocio_impl_glsl.cc +++ b/intern/opencolorio/ocio_impl_glsl.cc @@ -515,7 +515,8 @@ static void updateGPUDisplayParameters(OCIO_GPUShader &shader, float exponent, float dither, bool use_predivide, - bool use_overlay) + bool use_overlay, + bool use_hdr) { bool do_update = false; if (shader.parameters_buffer == nullptr) { @@ -543,6 +544,10 @@ static void updateGPUDisplayParameters(OCIO_GPUShader &shader, data.use_overlay = use_overlay; do_update = true; } + if (bool(data.use_hdr) != use_hdr) { + data.use_hdr = use_hdr; + do_update = true; + } if (do_update) { GPU_uniformbuf_update(shader.parameters_buffer, &data); } @@ -684,7 +689,8 @@ bool OCIOImpl::gpuDisplayShaderBind(OCIO_ConstConfigRcPtr *config, const float exponent, const float dither, const bool use_predivide, - const bool use_overlay) + const bool use_overlay, + const bool use_hdr) { /* Get GPU shader from cache or create new one. */ OCIO_GPUDisplayShader &display_shader = getGPUDisplayShader( @@ -719,7 +725,7 @@ bool OCIOImpl::gpuDisplayShaderBind(OCIO_ConstConfigRcPtr *config, GPU_uniformbuf_bind(textures.uniforms_buffer, UNIFORMBUF_SLOT_LUTS); } - updateGPUDisplayParameters(shader, scale, exponent, dither, use_predivide, use_overlay); + updateGPUDisplayParameters(shader, scale, exponent, dither, use_predivide, use_overlay, use_hdr); GPU_uniformbuf_bind(shader.parameters_buffer, UNIFORMBUF_SLOT_DISPLAY); /* TODO(fclem): remove remains of IMM. */ diff --git a/intern/opencolorio/ocio_shader_shared.hh b/intern/opencolorio/ocio_shader_shared.hh index f96c37d66c8..a91a959a477 100644 --- a/intern/opencolorio/ocio_shader_shared.hh +++ b/intern/opencolorio/ocio_shader_shared.hh @@ -36,7 +36,7 @@ struct OCIO_GPUParameters { float exponent; bool1 use_predivide; bool1 use_overlay; + bool1 use_hdr; int _pad0; int _pad1; - int _pad2; }; diff --git a/scripts/startup/bl_ui/properties_render.py b/scripts/startup/bl_ui/properties_render.py index 78d417a3bc3..51d25c744d5 100644 --- a/scripts/startup/bl_ui/properties_render.py +++ b/scripts/startup/bl_ui/properties_render.py @@ -58,6 +58,7 @@ class RENDER_PT_color_management(RenderButtonsPanel, Panel): 'BLENDER_WORKBENCH_NEXT'} def draw(self, context): + layout = self.layout layout.use_property_split = True layout.use_property_decorate = False # No animation. @@ -84,6 +85,36 @@ class RENDER_PT_color_management(RenderButtonsPanel, Panel): col.prop(scene.sequencer_colorspace_settings, "name", text="Sequencer") +class RENDER_PT_color_management_display_settings(RenderButtonsPanel, Panel): + bl_label = "Display" + bl_parent_id = "RENDER_PT_color_management" + bl_options = {'DEFAULT_CLOSED'} + COMPAT_ENGINES = { + 'BLENDER_RENDER', + 'BLENDER_EEVEE', + 'BLENDER_EEVEE_NEXT', + 'BLENDER_WORKBENCH', + 'BLENDER_WORKBENCH_NEXT'} + + def draw(self, context): + layout = self.layout + layout.use_property_split = True + layout.use_property_decorate = False # No animation. + + scene = context.scene + view = scene.view_settings + + # Only enable display sub-section if HDR support is available. + import gpu + layout.enabled = gpu.capabilities.hdr_support_get() + + # Only display HDR toggle for non-Filmic display transforms. + col = layout.column(align=True) + sub = col.row() + sub.active = (view.view_transform != "Filmic" and view.view_transform != "Filmic Log") + sub.prop(view, "use_hdr_view") + + class RENDER_PT_color_management_curves(RenderButtonsPanel, Panel): bl_label = "Use Curves" bl_parent_id = "RENDER_PT_color_management" @@ -1223,6 +1254,7 @@ classes = ( RENDER_PT_opengl_film, RENDER_PT_hydra_debug, RENDER_PT_color_management, + RENDER_PT_color_management_display_settings, RENDER_PT_color_management_curves, RENDER_PT_simplify, RENDER_PT_simplify_viewport, diff --git a/source/blender/editors/space_view3d/view3d_draw.cc b/source/blender/editors/space_view3d/view3d_draw.cc index ef2a8d98f2d..c76bc50c4bc 100644 --- a/source/blender/editors/space_view3d/view3d_draw.cc +++ b/source/blender/editors/space_view3d/view3d_draw.cc @@ -64,6 +64,7 @@ #include "GPU_batch.h" #include "GPU_batch_presets.h" +#include "GPU_capabilities.h" #include "GPU_framebuffer.h" #include "GPU_immediate.h" #include "GPU_immediate_util.h" @@ -1902,7 +1903,16 @@ ImBuf *ED_view3d_draw_offscreen_imbuf(Depsgraph *depsgraph, bool is_ortho = false; float winmat[4][4]; - if (ofs && ((GPU_offscreen_width(ofs) != sizex) || (GPU_offscreen_height(ofs) != sizey))) { + /* Determine desired offscreen format depending on HDR availability. */ + bool use_hdr = false; + if (scene && ((scene->view_settings.flag & COLORMANAGE_VIEW_USE_HDR) != 0)) { + use_hdr = GPU_hdr_support(); + } + eGPUTextureFormat desired_format = (use_hdr) ? GPU_RGBA16F : GPU_RGBA8; + + if (ofs && ((GPU_offscreen_width(ofs) != sizex) || (GPU_offscreen_height(ofs) != sizey) || + (GPU_offscreen_format(ofs) != desired_format))) + { /* sizes differ, can't reuse */ ofs = nullptr; } @@ -1921,7 +1931,7 @@ ImBuf *ED_view3d_draw_offscreen_imbuf(Depsgraph *depsgraph, ofs = GPU_offscreen_create(sizex, sizey, true, - GPU_RGBA8, + desired_format, GPU_TEXTURE_USAGE_SHADER_READ | GPU_TEXTURE_USAGE_HOST_READ, err_out); if (ofs == nullptr) { @@ -2392,7 +2402,7 @@ void ED_view3d_depth_override(Depsgraph *depsgraph, rv3d->rflag |= RV3D_ZOFFSET_DISABLED; /* Needed in cases the 3D Viewport isn't already setup. */ - WM_draw_region_viewport_ensure(region, SPACE_VIEW3D); + WM_draw_region_viewport_ensure(scene, region, SPACE_VIEW3D); WM_draw_region_viewport_bind(region); GPUViewport *viewport = WM_draw_region_get_viewport(region); diff --git a/source/blender/gpu/GPU_capabilities.h b/source/blender/gpu/GPU_capabilities.h index 7ba6f6c0536..ef2123f9754 100644 --- a/source/blender/gpu/GPU_capabilities.h +++ b/source/blender/gpu/GPU_capabilities.h @@ -52,6 +52,7 @@ bool GPU_compute_shader_support(void); bool GPU_shader_storage_buffer_objects_support(void); bool GPU_shader_image_load_store_support(void); bool GPU_shader_draw_parameters_support(void); +bool GPU_hdr_support(void); bool GPU_mem_stats_supported(void); void GPU_mem_stats_get(int *totalmem, int *freemem); diff --git a/source/blender/gpu/GPU_framebuffer.h b/source/blender/gpu/GPU_framebuffer.h index 4d7e7755648..dd233e79691 100644 --- a/source/blender/gpu/GPU_framebuffer.h +++ b/source/blender/gpu/GPU_framebuffer.h @@ -653,6 +653,11 @@ int GPU_offscreen_height(const GPUOffScreen *offscreen); */ struct GPUTexture *GPU_offscreen_color_texture(const GPUOffScreen *offscreen); +/** + * Return the texture format of a #GPUOffScreen. + */ +eGPUTextureFormat GPU_offscreen_format(const GPUOffScreen *offscreen); + /** * Return the internals of a #GPUOffScreen. Does not give ownership. * \note only to be used by viewport code! diff --git a/source/blender/gpu/intern/gpu_capabilities.cc b/source/blender/gpu/intern/gpu_capabilities.cc index 471040f401b..0b4ac4a7678 100644 --- a/source/blender/gpu/intern/gpu_capabilities.cc +++ b/source/blender/gpu/intern/gpu_capabilities.cc @@ -182,6 +182,11 @@ bool GPU_shader_draw_parameters_support() return GCaps.shader_draw_parameters_support; } +bool GPU_hdr_support() +{ + return GCaps.hdr_viewport_support; +} + int GPU_max_shader_storage_buffer_bindings() { return GCaps.max_shader_storage_buffer_bindings; diff --git a/source/blender/gpu/intern/gpu_capabilities_private.hh b/source/blender/gpu/intern/gpu_capabilities_private.hh index 8134c39871d..4598ad2198d 100644 --- a/source/blender/gpu/intern/gpu_capabilities_private.hh +++ b/source/blender/gpu/intern/gpu_capabilities_private.hh @@ -48,6 +48,7 @@ struct GPUCapabilities { bool shader_image_load_store_support = false; bool shader_draw_parameters_support = false; bool transform_feedback_support = false; + bool hdr_viewport_support = false; /* OpenGL related workarounds. */ bool mip_render_workaround = false; diff --git a/source/blender/gpu/intern/gpu_framebuffer.cc b/source/blender/gpu/intern/gpu_framebuffer.cc index 35fb841d80b..8e706de30d4 100644 --- a/source/blender/gpu/intern/gpu_framebuffer.cc +++ b/source/blender/gpu/intern/gpu_framebuffer.cc @@ -785,6 +785,11 @@ GPUTexture *GPU_offscreen_color_texture(const GPUOffScreen *ofs) return ofs->color; } +eGPUTextureFormat GPU_offscreen_format(const GPUOffScreen *offscreen) +{ + return GPU_texture_format(offscreen->color); +} + void GPU_offscreen_viewport_data_get(GPUOffScreen *ofs, GPUFrameBuffer **r_fb, GPUTexture **r_color, diff --git a/source/blender/gpu/intern/gpu_viewport.cc b/source/blender/gpu/intern/gpu_viewport.cc index 4b56e8e4ea8..587c09e9cb8 100644 --- a/source/blender/gpu/intern/gpu_viewport.cc +++ b/source/blender/gpu/intern/gpu_viewport.cc @@ -441,6 +441,8 @@ static void gpu_viewport_draw_colormanaged(GPUViewport *viewport, GPUTexture *color_overlay = viewport->color_overlay_tx[view]; bool use_ocio = false; + bool use_hdr = GPU_hdr_support() && + ((viewport->view_settings.flag & COLORMANAGE_VIEW_USE_HDR) != 0); if (viewport->do_color_management && display_colorspace) { /* During the binding process the last used VertexFormat is tested and can assert as it is not @@ -467,6 +469,7 @@ static void gpu_viewport_draw_colormanaged(GPUViewport *viewport, GPU_batch_program_set_builtin(batch, GPU_SHADER_2D_IMAGE_OVERLAYS_MERGE); GPU_batch_uniform_1i(batch, "overlay", do_overlay_merge); GPU_batch_uniform_1i(batch, "display_transform", display_colorspace); + GPU_batch_uniform_1i(batch, "use_hdr", use_hdr); } GPU_texture_bind(color, 0); diff --git a/source/blender/gpu/metal/mtl_backend.mm b/source/blender/gpu/metal/mtl_backend.mm index cb9109dc29d..a2149ff917a 100644 --- a/source/blender/gpu/metal/mtl_backend.mm +++ b/source/blender/gpu/metal/mtl_backend.mm @@ -420,6 +420,7 @@ void MTLBackend::capabilities_init(MTLContext *ctx) GCaps.compute_shader_support = true; GCaps.shader_storage_buffer_objects_support = true; GCaps.shader_draw_parameters_support = true; + GCaps.hdr_viewport_support = true; GCaps.geometry_shader_support = false; diff --git a/source/blender/gpu/opengl/gl_backend.cc b/source/blender/gpu/opengl/gl_backend.cc index 3de20a0fbf6..f1a9d6004b6 100644 --- a/source/blender/gpu/opengl/gl_backend.cc +++ b/source/blender/gpu/opengl/gl_backend.cc @@ -234,6 +234,7 @@ static void detect_workarounds() GCaps.shader_image_load_store_support = false; GCaps.shader_draw_parameters_support = false; GCaps.shader_storage_buffer_objects_support = false; + GCaps.hdr_viewport_support = false; GLContext::base_instance_support = false; GLContext::clear_texture_support = false; GLContext::copy_image_support = false; @@ -546,6 +547,7 @@ void GLBackend::capabilities_init() epoxy_gl_version() >= 43; GCaps.geometry_shader_support = true; GCaps.max_samplers = GCaps.max_textures; + GCaps.hdr_viewport_support = false; if (GCaps.compute_shader_support) { glGetIntegeri_v(GL_MAX_COMPUTE_WORK_GROUP_COUNT, 0, &GCaps.max_work_group_count[0]); diff --git a/source/blender/gpu/shaders/gpu_shader_image_overlays_merge_frag.glsl b/source/blender/gpu/shaders/gpu_shader_image_overlays_merge_frag.glsl index d77f073b7de..5ed519c0e93 100644 --- a/source/blender/gpu/shaders/gpu_shader_image_overlays_merge_frag.glsl +++ b/source/blender/gpu/shaders/gpu_shader_image_overlays_merge_frag.glsl @@ -36,7 +36,15 @@ void main() vec4 overlay_col = texture(overlays_texture, texCoord_interp.xy); if (overlay) { - fragColor = clamp(fragColor, 0.0, 1.0); + if (!use_hdr) { + /* If we're not using an extended colour space, clamp the color 0..1. */ + fragColor = clamp(fragColor, 0.0, 1.0); + } + else { + /* When using extended colorspace, interpolate towards clamped color to improve display of + * alpha-blended overlays. */ + fragColor = mix(max(fragColor, 0.0), clamp(fragColor, 0.0, 1.0), overlay_col.a); + } fragColor *= 1.0 - overlay_col.a; fragColor += overlay_col; } diff --git a/source/blender/gpu/shaders/infos/gpu_shader_2D_image_overlays_merge_info.hh b/source/blender/gpu/shaders/infos/gpu_shader_2D_image_overlays_merge_info.hh index ea32d9d5f5d..30e205ed14c 100644 --- a/source/blender/gpu/shaders/infos/gpu_shader_2D_image_overlays_merge_info.hh +++ b/source/blender/gpu/shaders/infos/gpu_shader_2D_image_overlays_merge_info.hh @@ -17,6 +17,7 @@ GPU_SHADER_CREATE_INFO(gpu_shader_2D_image_overlays_merge) .push_constant(Type::MAT4, "ModelViewProjectionMatrix") .push_constant(Type::BOOL, "display_transform") .push_constant(Type::BOOL, "overlay") + .push_constant(Type::BOOL, "use_hdr") /* Sampler slots should match OCIO's. */ .sampler(0, ImageType::FLOAT_2D, "image_texture") .sampler(1, ImageType::FLOAT_2D, "overlays_texture") diff --git a/source/blender/imbuf/IMB_colormanagement.h b/source/blender/imbuf/IMB_colormanagement.h index 38331fae795..497fd5f3f7a 100644 --- a/source/blender/imbuf/IMB_colormanagement.h +++ b/source/blender/imbuf/IMB_colormanagement.h @@ -297,6 +297,15 @@ void IMB_display_buffer_transform_apply(unsigned char *display_buffer, const struct ColorManagedViewSettings *view_settings, const struct ColorManagedDisplaySettings *display_settings, bool predivide); +void IMB_display_buffer_transform_apply_float( + float *float_display_buffer, + float *linear_buffer, + int width, + int height, + int channels, + const struct ColorManagedViewSettings *view_settings, + const struct ColorManagedDisplaySettings *display_settings, + bool predivide); void IMB_display_buffer_release(void *cache_handle); diff --git a/source/blender/imbuf/intern/colormanagement.cc b/source/blender/imbuf/intern/colormanagement.cc index 930d0d4b6d2..802f98849c2 100644 --- a/source/blender/imbuf/intern/colormanagement.cc +++ b/source/blender/imbuf/intern/colormanagement.cc @@ -42,6 +42,8 @@ #include "BKE_image_format.h" #include "BKE_main.h" +#include "GPU_capabilities.h" + #include "RNA_define.h" #include "SEQ_iterator.h" @@ -2790,6 +2792,31 @@ void IMB_display_buffer_transform_apply(uchar *display_buffer, MEM_freeN(buffer); } +void IMB_display_buffer_transform_apply_float(float *float_display_buffer, + float *linear_buffer, + int width, + int height, + int channels, + const ColorManagedViewSettings *view_settings, + const ColorManagedDisplaySettings *display_settings, + bool predivide) +{ + float *buffer; + ColormanageProcessor *cm_processor = IMB_colormanagement_display_processor_new(view_settings, + display_settings); + + buffer = static_cast(MEM_mallocN(size_t(channels) * width * height * sizeof(float), + "display transform temp buffer")); + memcpy(buffer, linear_buffer, size_t(channels) * width * height * sizeof(float)); + + IMB_colormanagement_processor_apply(cm_processor, buffer, width, height, channels, predivide); + + IMB_colormanagement_processor_free(cm_processor); + + memcpy(float_display_buffer, buffer, size_t(channels) * width * height * sizeof(float)); + MEM_freeN(buffer); +} + void IMB_display_buffer_release(void *cache_handle) { if (cache_handle) { @@ -4080,6 +4107,8 @@ bool IMB_colormanagement_setup_glsl_draw_from_space( const float gamma = applied_view_settings->gamma; const float scale = (exposure == 0.0f) ? 1.0f : powf(2.0f, exposure); const float exponent = (gamma == 1.0f) ? 1.0f : 1.0f / max_ff(FLT_EPSILON, gamma); + const bool use_hdr = GPU_hdr_support() && + (applied_view_settings->flag & COLORMANAGE_VIEW_USE_HDR) != 0; OCIO_ConstConfigRcPtr *config = OCIO_getCurrentConfig(); @@ -4094,7 +4123,8 @@ bool IMB_colormanagement_setup_glsl_draw_from_space( exponent, dither, predivide, - do_overlay_merge); + do_overlay_merge, + use_hdr); OCIO_configRelease(config); diff --git a/source/blender/makesdna/DNA_color_types.h b/source/blender/makesdna/DNA_color_types.h index 89ce1d8392c..f05ddf303b6 100644 --- a/source/blender/makesdna/DNA_color_types.h +++ b/source/blender/makesdna/DNA_color_types.h @@ -207,6 +207,7 @@ typedef struct ColorManagedColorspaceSettings { /** #ColorManagedViewSettings.flag */ enum { COLORMANAGE_VIEW_USE_CURVES = (1 << 0), + COLORMANAGE_VIEW_USE_HDR = (1 << 1), }; #ifdef __cplusplus diff --git a/source/blender/makesrna/intern/rna_color.cc b/source/blender/makesrna/intern/rna_color.cc index 133aaa41715..6ac71adba60 100644 --- a/source/blender/makesrna/intern/rna_color.cc +++ b/source/blender/makesrna/intern/rna_color.cc @@ -1273,6 +1273,16 @@ static void rna_def_colormanage(BlenderRNA *brna) RNA_def_property_ui_text(prop, "Use Curves", "Use RGB curved for pre-display transformation"); RNA_def_property_update(prop, NC_WINDOW, "rna_ColorManagement_update"); + prop = RNA_def_property(srna, "use_hdr_view", PROP_BOOLEAN, PROP_NONE); + RNA_def_property_boolean_sdna(prop, NULL, "flag", COLORMANAGE_VIEW_USE_HDR); + RNA_def_property_ui_text( + prop, + "High Dynamic Range", + "Enable high dynamic range display in rendered viewport, uncapping display brightness. This " + "requires a monitor with HDR support and a view transform designed for HDR. " + "'Filmic' does not generate HDR colors"); + RNA_def_property_update(prop, NC_WINDOW, "rna_ColorManagedColorspaceSettings_reload_update"); + /* ** Color-space ** */ srna = RNA_def_struct(brna, "ColorManagedInputColorspaceSettings", nullptr); RNA_def_struct_path_func(srna, "rna_ColorManagedInputColorspaceSettings_path"); diff --git a/source/blender/python/gpu/gpu_py_capabilities.cc b/source/blender/python/gpu/gpu_py_capabilities.cc index 1e1e327b126..4c2a5d3d80a 100644 --- a/source/blender/python/gpu/gpu_py_capabilities.cc +++ b/source/blender/python/gpu/gpu_py_capabilities.cc @@ -228,6 +228,19 @@ static PyObject *pygpu_shader_image_load_store_support_get(PyObject * /*self*/) { return PyBool_FromLong(GPU_shader_image_load_store_support()); } + +PyDoc_STRVAR(pygpu_hdr_support_get_doc, + ".. function:: hdr_support_get()\n" + "\n" + " Return whether GPU backend supports High Dynamic range for viewport.\n" + "\n" + " :return: HDR support available.\n" + " :rtype: bool\n"); +static PyObject *pygpu_hdr_support_get(PyObject * /*self*/) +{ + return PyBool_FromLong(GPU_hdr_support()); +} + /** \} */ /* -------------------------------------------------------------------- */ @@ -302,6 +315,10 @@ static PyMethodDef pygpu_capabilities__tp_methods[] = { (PyCFunction)pygpu_shader_image_load_store_support_get, METH_NOARGS, pygpu_shader_image_load_store_support_get_doc}, + {"hdr_support_get", + (PyCFunction)pygpu_hdr_support_get, + METH_NOARGS, + pygpu_hdr_support_get_doc}, {nullptr, nullptr, 0, nullptr}, }; diff --git a/source/blender/render/RE_pipeline.h b/source/blender/render/RE_pipeline.h index 23bd499dea6..525b17ac479 100644 --- a/source/blender/render/RE_pipeline.h +++ b/source/blender/render/RE_pipeline.h @@ -245,6 +245,7 @@ struct RenderStats *RE_GetStats(struct Render *re); * Caller is responsible for allocating `rect` in correct size! */ void RE_ResultGet32(struct Render *re, unsigned int *rect); +void RE_ResultGetFloat(struct Render *re, float *rect); void RE_render_result_full_channel_name(char *fullname, const char *layname, diff --git a/source/blender/windowmanager/WM_api.hh b/source/blender/windowmanager/WM_api.hh index 1c53cc59417..d3dda5cda6f 100644 --- a/source/blender/windowmanager/WM_api.hh +++ b/source/blender/windowmanager/WM_api.hh @@ -1597,7 +1597,7 @@ void *WM_draw_cb_activate(wmWindow *win, void (*draw)(const wmWindow *, void *), void WM_draw_cb_exit(wmWindow *win, void *handle); void WM_redraw_windows(bContext *C); -void WM_draw_region_viewport_ensure(ARegion *region, short space_type); +void WM_draw_region_viewport_ensure(Scene *scene, ARegion *region, short space_type); void WM_draw_region_viewport_bind(ARegion *region); void WM_draw_region_viewport_unbind(ARegion *region); diff --git a/source/blender/windowmanager/intern/wm_draw.cc b/source/blender/windowmanager/intern/wm_draw.cc index b5f8ab5b4cf..120e79d8d84 100644 --- a/source/blender/windowmanager/intern/wm_draw.cc +++ b/source/blender/windowmanager/intern/wm_draw.cc @@ -39,6 +39,7 @@ #include "ED_view3d.hh" #include "GPU_batch_presets.h" +#include "GPU_capabilities.h" #include "GPU_context.h" #include "GPU_debug.h" #include "GPU_framebuffer.h" @@ -662,8 +663,25 @@ static void wm_draw_offscreen_texture_parameters(GPUOffScreen *offscreen) GPU_texture_mipmap_mode(texture, false, false); } -static void wm_draw_region_buffer_create(ARegion *region, bool stereo, bool use_viewport) +static eGPUTextureFormat get_hdr_framebuffer_format(const Scene *scene) { + bool use_hdr = false; + if (scene && ((scene->view_settings.flag & COLORMANAGE_VIEW_USE_HDR) != 0)) { + use_hdr = GPU_hdr_support(); + } + eGPUTextureFormat desired_format = (use_hdr) ? GPU_RGBA16F : GPU_RGBA8; + return desired_format; +} + +static void wm_draw_region_buffer_create(Scene *scene, + ARegion *region, + bool stereo, + bool use_viewport) +{ + + /* Determine desired offscreen format depending on HDR availability. */ + eGPUTextureFormat desired_format = get_hdr_framebuffer_format(scene); + if (region->draw_buffer) { if (region->draw_buffer->stereo != stereo) { /* Free draw buffer on stereo changes. */ @@ -673,7 +691,8 @@ static void wm_draw_region_buffer_create(ARegion *region, bool stereo, bool use_ /* Free offscreen buffer on size changes. Viewport auto resizes. */ GPUOffScreen *offscreen = region->draw_buffer->offscreen; if (offscreen && (GPU_offscreen_width(offscreen) != region->winx || - GPU_offscreen_height(offscreen) != region->winy)) + GPU_offscreen_height(offscreen) != region->winy || + GPU_offscreen_format(offscreen) != desired_format)) { wm_draw_region_buffer_free(region); } @@ -692,8 +711,12 @@ static void wm_draw_region_buffer_create(ARegion *region, bool stereo, bool use_ /* Allocate off-screen buffer if it does not exist. This one has no * depth or multi-sample buffers. 3D view creates own buffers with * the data it needs. */ - GPUOffScreen *offscreen = GPU_offscreen_create( - region->winx, region->winy, false, GPU_RGBA8, GPU_TEXTURE_USAGE_SHADER_READ, nullptr); + GPUOffScreen *offscreen = GPU_offscreen_create(region->winx, + region->winy, + false, + desired_format, + GPU_TEXTURE_USAGE_SHADER_READ, + nullptr); if (!offscreen) { WM_report(RPT_ERROR, "Region could not be drawn!"); return; @@ -946,7 +969,8 @@ static void wm_draw_window_offscreen(bContext *C, wmWindow *win, bool stereo) GPU_debug_group_begin(use_viewport ? "Viewport" : "ARegion"); if (stereo && wm_draw_region_stereo_set(bmain, area, region, STEREO_LEFT_ID)) { - wm_draw_region_buffer_create(region, true, use_viewport); + Scene *scene = WM_window_get_active_scene(win); + wm_draw_region_buffer_create(scene, region, true, use_viewport); for (int view = 0; view < 2; view++) { eStereoViews sview; @@ -969,7 +993,8 @@ static void wm_draw_window_offscreen(bContext *C, wmWindow *win, bool stereo) } else { wm_draw_region_stereo_set(bmain, area, region, STEREO_LEFT_ID); - wm_draw_region_buffer_create(region, false, use_viewport); + Scene *scene = WM_window_get_active_scene(win); + wm_draw_region_buffer_create(scene, region, false, use_viewport); wm_draw_region_bind(region, 0); ED_region_do_draw(C, region); wm_draw_region_unbind(region); @@ -1002,7 +1027,8 @@ static void wm_draw_window_offscreen(bContext *C, wmWindow *win, bool stereo) region->type->layout(C, region); } - wm_draw_region_buffer_create(region, false, false); + Scene *scene = WM_window_get_active_scene(win); + wm_draw_region_buffer_create(scene, region, false, false); wm_draw_region_bind(region, 0); GPU_clear_color(0.0f, 0.0f, 0.0f, 0.0f); ED_region_do_draw(C, region); @@ -1157,13 +1183,16 @@ static void wm_draw_window(bContext *C, wmWindow *win) wm_draw_window_onscreen(C, win, -1); } else { + /* Determine desired offscreen format depending on HDR availability. */ + eGPUTextureFormat desired_format = get_hdr_framebuffer_format(WM_window_get_active_scene(win)); + /* For side-by-side and top-bottom, we need to render each view to an * an off-screen texture and then draw it. This used to happen for all * stereo methods, but it's less efficient than drawing directly. */ const int width = WM_window_pixels_x(win); const int height = WM_window_pixels_y(win); GPUOffScreen *offscreen = GPU_offscreen_create( - width, height, false, GPU_RGBA8, GPU_TEXTURE_USAGE_SHADER_READ, nullptr); + width, height, false, desired_format, GPU_TEXTURE_USAGE_SHADER_READ, nullptr); if (offscreen) { GPUTexture *texture = GPU_offscreen_color_texture(offscreen); @@ -1314,8 +1343,11 @@ uint8_t *WM_window_pixels_read_from_offscreen(bContext *C, wmWindow *win, int r_ r_size[0] = WM_window_pixels_x(win); r_size[1] = WM_window_pixels_y(win); + /* Determine desired offscreen format depending on HDR availability. */ + eGPUTextureFormat desired_format = get_hdr_framebuffer_format(WM_window_get_active_scene(win)); + GPUOffScreen *offscreen = GPU_offscreen_create( - r_size[0], r_size[1], false, GPU_RGBA8, GPU_TEXTURE_USAGE_SHADER_READ, nullptr); + r_size[0], r_size[1], false, desired_format, GPU_TEXTURE_USAGE_SHADER_READ, nullptr); if (UNLIKELY(!offscreen)) { return nullptr; } @@ -1557,7 +1589,9 @@ void wm_draw_region_test(bContext *C, ScrArea *area, ARegion *region) { /* Function for redraw timer benchmark. */ bool use_viewport = WM_region_use_viewport(area, region); - wm_draw_region_buffer_create(region, false, use_viewport); + wmWindow *win = CTX_wm_window(C); + Scene *scene = WM_window_get_active_scene(win); + wm_draw_region_buffer_create(scene, region, false, use_viewport); wm_draw_region_bind(region, 0); ED_region_do_draw(C, region); wm_draw_region_unbind(region); @@ -1589,10 +1623,10 @@ void WM_redraw_windows(bContext *C) * * \{ */ -void WM_draw_region_viewport_ensure(ARegion *region, short space_type) +void WM_draw_region_viewport_ensure(Scene *scene, ARegion *region, short space_type) { bool use_viewport = wm_region_use_viewport_by_type(space_type, region->regiontype); - wm_draw_region_buffer_create(region, false, use_viewport); + wm_draw_region_buffer_create(scene, region, false, use_viewport); } void WM_draw_region_viewport_bind(ARegion *region)