/* * Adapted from OpenColorIO with this license: * * Copyright (c) 2003-2010 Sony Pictures Imageworks Inc., et al. * All Rights Reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are * met: * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * Neither the name of Sony Pictures Imageworks nor the names of its * contributors may be used to endorse or promote products derived from * this software without specific prior written permission. * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * * Modifications Copyright 2013, Blender Foundation. */ #include #include #include #include #include #ifdef _MSC_VER # pragma warning(push) # pragma warning(disable : 4251 4275) #endif #include #ifdef _MSC_VER # pragma warning(pop) #endif #include "GPU_immediate.h" #include "GPU_shader.h" #include "GPU_uniform_buffer.h" using namespace OCIO_NAMESPACE; #include "MEM_guardedalloc.h" #include "ocio_impl.h" extern "C" char datatoc_gpu_shader_display_transform_glsl[]; extern "C" char datatoc_gpu_shader_display_transform_vertex_glsl[]; /* **** OpenGL drawing routines using GLSL for color space transform ***** */ enum OCIO_GPUTextureSlots { TEXTURE_SLOT_IMAGE = 0, TEXTURE_SLOT_OVERLAY = 1, TEXTURE_SLOT_CURVE_MAPPING = 2, TEXTURE_SLOT_LUTS_OFFSET = 3, }; /* Curve mapping parameters * * See documentation for OCIO_CurveMappingSettings to get fields descriptions. * (this ones pretty much copies stuff from C structure.) */ struct OCIO_GPUCurveMappingParameters { float curve_mapping_mintable[4]; float curve_mapping_range[4]; float curve_mapping_ext_in_x[4]; float curve_mapping_ext_in_y[4]; float curve_mapping_ext_out_x[4]; float curve_mapping_ext_out_y[4]; float curve_mapping_first_x[4]; float curve_mapping_first_y[4]; float curve_mapping_last_x[4]; float curve_mapping_last_y[4]; float curve_mapping_black[4]; float curve_mapping_bwmul[4]; int curve_mapping_lut_size; int curve_mapping_use_extend_extrapolate; int _pad[2]; /** WARNING: Needs to be 16byte aligned. Used as UBO data. */ }; struct OCIO_GPUShader { /* GPU shader. */ struct GPUShader *shader = nullptr; /** Uniform locations. */ int scale_loc = 0; int exponent_loc = 0; int dither_loc = 0; int overlay_loc = 0; int predivide_loc = 0; int ubo_bind = 0; /* Destructor. */ ~OCIO_GPUShader() { if (shader) { GPU_shader_free(shader); } } }; struct OCIO_GPULutTexture { GPUTexture *texture = nullptr; std::string sampler_name; }; struct OCIO_GPUUniform { GpuShaderDesc::UniformData data; std::string name; }; struct OCIO_GPUTextures { /** LUT Textures */ std::vector luts; /* Dummy in case of no overlay. */ GPUTexture *dummy = nullptr; /* Uniforms */ std::vector uniforms; /* Destructor. */ ~OCIO_GPUTextures() { for (OCIO_GPULutTexture &lut : luts) { GPU_texture_free(lut.texture); } if (dummy) { GPU_texture_free(dummy); } } }; struct OCIO_GPUCurveMappping { /** GPU Uniform Buffer handle. 0 if not allocated. */ GPUUniformBuf *buffer = nullptr; /** OpenGL Texture handles. 0 if not allocated. */ GPUTexture *texture = nullptr; /* To detect when to update the uniforms and textures. */ size_t cache_id = 0; /* Destructor. */ ~OCIO_GPUCurveMappping() { if (texture) { GPU_texture_free(texture); } if (buffer) { GPU_uniformbuf_free(buffer); } } }; struct OCIO_GPUDisplayShader { OCIO_GPUShader shader; OCIO_GPUTextures textures; OCIO_GPUCurveMappping curvemap; /* Cache variables. */ std::string input; std::string view; std::string display; std::string look; bool use_curve_mapping = false; /** Error checking. */ bool valid = false; }; static const int SHADER_CACHE_MAX_SIZE = 4; std::list SHADER_CACHE; /* -------------------------------------------------------------------- */ /** \name Shader * \{ */ static bool createGPUShader(OCIO_GPUShader &shader, OCIO_GPUTextures &textures, const GpuShaderDescRcPtr &shaderdesc_to_scene_linear, const GpuShaderDescRcPtr &shaderdesc_to_display, const bool use_curve_mapping) { std::ostringstream os; { /* Fragment shader */ /* Work around OpenColorIO not supporting latest GLSL yet. */ os << "#define texture2D texture\n"; os << "#define texture3D texture\n"; if (use_curve_mapping) { os << "#define USE_CURVE_MAPPING\n"; } os << shaderdesc_to_scene_linear->getShaderText() << "\n"; os << shaderdesc_to_display->getShaderText() << "\n"; os << datatoc_gpu_shader_display_transform_glsl; } shader.shader = GPU_shader_create(datatoc_gpu_shader_display_transform_vertex_glsl, os.str().c_str(), nullptr, nullptr, nullptr, "OCIOShader"); if (shader.shader == nullptr) { return false; } shader.scale_loc = GPU_shader_get_uniform(shader.shader, "scale"); shader.exponent_loc = GPU_shader_get_uniform(shader.shader, "exponent"); shader.dither_loc = GPU_shader_get_uniform(shader.shader, "dither"); shader.overlay_loc = GPU_shader_get_uniform(shader.shader, "overlay"); shader.predivide_loc = GPU_shader_get_uniform(shader.shader, "predivide"); shader.ubo_bind = GPU_shader_get_uniform_block_binding(shader.shader, "OCIO_GPUCurveMappingParameters"); GPU_shader_bind(shader.shader); /* Set texture bind point uniform once. This is saved by the shader. */ GPUShader *sh = shader.shader; GPU_shader_uniform_int(sh, GPU_shader_get_uniform(sh, "image_texture"), TEXTURE_SLOT_IMAGE); GPU_shader_uniform_int(sh, GPU_shader_get_uniform(sh, "overlay_texture"), TEXTURE_SLOT_OVERLAY); if (use_curve_mapping) { GPU_shader_uniform_int( sh, GPU_shader_get_uniform(sh, "curve_mapping_texture"), TEXTURE_SLOT_CURVE_MAPPING); } /* Set LUT textures. */ for (int i = 0; i < textures.luts.size(); i++) { GPU_shader_uniform_int(sh, GPU_shader_get_uniform(sh, textures.luts[i].sampler_name.c_str()), TEXTURE_SLOT_LUTS_OFFSET + i); } /* Set uniforms. */ for (OCIO_GPUUniform &uniform : textures.uniforms) { const GpuShaderDesc::UniformData &data = uniform.data; const char *name = uniform.name.c_str(); if (data.m_getDouble) { GPU_shader_uniform_1f(sh, name, (float)data.m_getDouble()); } else if (data.m_getBool) { GPU_shader_uniform_1f(sh, name, (float)(data.m_getBool() ? 1.0f : 0.0f)); } else if (data.m_getFloat3) { GPU_shader_uniform_3f(sh, name, (float)data.m_getFloat3()[0], (float)data.m_getFloat3()[1], (float)data.m_getFloat3()[2]); } else if (data.m_vectorFloat.m_getSize && data.m_vectorFloat.m_getVector) { GPU_shader_uniform_vector(sh, GPU_shader_get_uniform(sh, name), (int)data.m_vectorFloat.m_getSize(), 1, (float *)data.m_vectorFloat.m_getVector()); } else if (data.m_vectorInt.m_getSize && data.m_vectorInt.m_getVector) { GPU_shader_uniform_vector_int(sh, GPU_shader_get_uniform(sh, name), (int)data.m_vectorInt.m_getSize(), 1, (int *)data.m_vectorInt.m_getVector()); } } return true; } /** \} */ /* -------------------------------------------------------------------- */ /** \name Textures * \{ */ static bool addGPUUniform(OCIO_GPUTextures &textures, const GpuShaderDescRcPtr &shader_desc, int index) { OCIO_GPUUniform uniform; uniform.name = shader_desc->getUniform(index, uniform.data); if (uniform.data.m_type == UNIFORM_UNKNOWN) { return false; } textures.uniforms.push_back(uniform); return true; } static bool addGPULut2D(OCIO_GPUTextures &textures, const GpuShaderDescRcPtr &shader_desc, int index) { const char *texture_name = nullptr; const char *sampler_name = nullptr; unsigned int width = 0; unsigned int height = 0; GpuShaderCreator::TextureType channel = GpuShaderCreator::TEXTURE_RGB_CHANNEL; Interpolation interpolation = INTERP_LINEAR; shader_desc->getTexture( index, texture_name, sampler_name, width, height, channel, interpolation); const float *values; shader_desc->getTextureValues(index, values); if (texture_name == nullptr || sampler_name == nullptr || width == 0 || height == 0 || values == nullptr) { return false; } eGPUTextureFormat format = (channel == GpuShaderCreator::TEXTURE_RGB_CHANNEL) ? GPU_RGB16F : GPU_R16F; OCIO_GPULutTexture lut; lut.texture = GPU_texture_create_2d(texture_name, width, height, 0, format, values); if (lut.texture == nullptr) { return false; } GPU_texture_filter_mode(lut.texture, interpolation != INTERP_NEAREST); GPU_texture_wrap_mode(lut.texture, false, true); lut.sampler_name = sampler_name; textures.luts.push_back(lut); return true; } static bool addGPULut3D(OCIO_GPUTextures &textures, const GpuShaderDescRcPtr &shader_desc, int index) { const char *texture_name = nullptr; const char *sampler_name = nullptr; unsigned int edgelen = 0; Interpolation interpolation = INTERP_LINEAR; shader_desc->get3DTexture(index, texture_name, sampler_name, edgelen, interpolation); const float *values; shader_desc->get3DTextureValues(index, values); if (texture_name == nullptr || sampler_name == nullptr || edgelen == 0 || values == nullptr) { return false; } OCIO_GPULutTexture lut; lut.texture = GPU_texture_create_3d( texture_name, edgelen, edgelen, edgelen, 0, GPU_RGB16F, GPU_DATA_FLOAT, values); if (lut.texture == nullptr) { return false; } GPU_texture_filter_mode(lut.texture, interpolation != INTERP_NEAREST); GPU_texture_wrap_mode(lut.texture, false, true); lut.sampler_name = sampler_name; textures.luts.push_back(lut); return true; } static bool createGPUTextures(OCIO_GPUTextures &textures, const GpuShaderDescRcPtr &shaderdesc_to_scene_linear, const GpuShaderDescRcPtr &shaderdesc_to_display) { textures.dummy = GPU_texture_create_error(2, false); textures.luts.clear(); textures.uniforms.clear(); for (int index = 0; index < shaderdesc_to_scene_linear->getNumUniforms(); index++) { if (!addGPUUniform(textures, shaderdesc_to_scene_linear, index)) { return false; } } for (int index = 0; index < shaderdesc_to_scene_linear->getNumTextures(); index++) { if (!addGPULut2D(textures, shaderdesc_to_scene_linear, index)) { return false; } } for (int index = 0; index < shaderdesc_to_scene_linear->getNum3DTextures(); index++) { if (!addGPULut3D(textures, shaderdesc_to_scene_linear, index)) { return false; } } for (int index = 0; index < shaderdesc_to_display->getNumUniforms(); index++) { if (!addGPUUniform(textures, shaderdesc_to_display, index)) { return false; } } for (int index = 0; index < shaderdesc_to_display->getNumTextures(); index++) { if (!addGPULut2D(textures, shaderdesc_to_display, index)) { return false; } } for (int index = 0; index < shaderdesc_to_display->getNum3DTextures(); index++) { if (!addGPULut3D(textures, shaderdesc_to_display, index)) { return false; } } return true; } /** \} */ /* -------------------------------------------------------------------- */ /** \name Curve Mapping * \{ */ static bool createGPUCurveMapping(OCIO_GPUCurveMappping &curvemap, OCIO_CurveMappingSettings *curve_mapping_settings) { if (curve_mapping_settings) { int lut_size = curve_mapping_settings->lut_size; curvemap.texture = GPU_texture_create_1d("OCIOCurveMap", lut_size, 1, GPU_RGBA16F, nullptr); GPU_texture_filter_mode(curvemap.texture, false); GPU_texture_wrap_mode(curvemap.texture, false, true); curvemap.buffer = GPU_uniformbuf_create(sizeof(OCIO_GPUCurveMappingParameters)); if (curvemap.texture == nullptr || curvemap.buffer == nullptr) { return false; } } return true; } static void updateGPUCurveMapping(OCIO_GPUCurveMappping &curvemap, OCIO_CurveMappingSettings *curve_mapping_settings) { /* Test if we need to update. The caller ensures the curve_mapping_settings * changes when its contents changes. */ if (curve_mapping_settings == nullptr || curvemap.cache_id == curve_mapping_settings->cache_id) { return; } curvemap.cache_id = curve_mapping_settings->cache_id; /* Update texture. */ int offset[3] = {0, 0, 0}; int extent[3] = {curve_mapping_settings->lut_size, 0, 0}; const float *pixels = curve_mapping_settings->lut; GPU_texture_update_sub( curvemap.texture, GPU_DATA_FLOAT, pixels, UNPACK3(offset), UNPACK3(extent)); /* Update uniforms. */ OCIO_GPUCurveMappingParameters data; for (int i = 0; i < 4; i++) { data.curve_mapping_range[i] = curve_mapping_settings->range[i]; data.curve_mapping_mintable[i] = curve_mapping_settings->mintable[i]; data.curve_mapping_ext_in_x[i] = curve_mapping_settings->ext_in_x[i]; data.curve_mapping_ext_in_y[i] = curve_mapping_settings->ext_in_y[i]; data.curve_mapping_ext_out_x[i] = curve_mapping_settings->ext_out_x[i]; data.curve_mapping_ext_out_y[i] = curve_mapping_settings->ext_out_y[i]; data.curve_mapping_first_x[i] = curve_mapping_settings->first_x[i]; data.curve_mapping_first_y[i] = curve_mapping_settings->first_y[i]; data.curve_mapping_last_x[i] = curve_mapping_settings->last_x[i]; data.curve_mapping_last_y[i] = curve_mapping_settings->last_y[i]; } for (int i = 0; i < 3; i++) { data.curve_mapping_black[i] = curve_mapping_settings->black[i]; data.curve_mapping_bwmul[i] = curve_mapping_settings->bwmul[i]; } data.curve_mapping_lut_size = curve_mapping_settings->lut_size; data.curve_mapping_use_extend_extrapolate = curve_mapping_settings->use_extend_extrapolate; GPU_uniformbuf_update(curvemap.buffer, &data); } /** \} */ /* -------------------------------------------------------------------- */ /** \name OCIO GPU Shader Implementation * \{ */ bool OCIOImpl::supportGPUShader() { /* Minimum supported version 3.3 does meet all requirements. */ return true; } static OCIO_GPUDisplayShader &getGPUDisplayShader( OCIO_ConstConfigRcPtr *config, const char *input, const char *view, const char *display, const char *look, OCIO_CurveMappingSettings *curve_mapping_settings) { /* Find existing shader in cache. */ const bool use_curve_mapping = (curve_mapping_settings != nullptr); for (std::list::iterator it = SHADER_CACHE.begin(); it != SHADER_CACHE.end(); it++) { if (it->input == input && it->view == view && it->display == display && it->look == look && it->use_curve_mapping == use_curve_mapping) { /* Move to front of the cache to mark as most recently used. */ if (it != SHADER_CACHE.begin()) { SHADER_CACHE.splice(SHADER_CACHE.begin(), SHADER_CACHE, it); } return *it; } } /* Remove least recently used element from cache. */ if (SHADER_CACHE.size() >= SHADER_CACHE_MAX_SIZE) { SHADER_CACHE.pop_back(); } /* Create GPU shader. */ SHADER_CACHE.emplace_front(); OCIO_GPUDisplayShader &display_shader = SHADER_CACHE.front(); display_shader.input = input; display_shader.view = view; display_shader.display = display; display_shader.look = look; display_shader.use_curve_mapping = use_curve_mapping; display_shader.valid = false; /* Create Processors. * * Scale and exponent are handled outside of OCIO shader so we can handle them * as uniforms at the binding stage. OCIO would otherwise bake them into the * shader code, requiring slow recompiles when interactively adjusting them. * * Note that OCIO does have the concept of dynamic properties, however there * is no dynamic gamma and exposure is part of more expensive operations only. * * Since exposure must happen in scene linear, we use two processors. The input * is usually scene linear already and so that conversion is often a no-op. */ OCIO_ConstProcessorRcPtr *processor_to_scene_linear = OCIO_configGetProcessorWithNames( config, input, ROLE_SCENE_LINEAR); OCIO_ConstProcessorRcPtr *processor_to_display = OCIO_createDisplayProcessor( config, ROLE_SCENE_LINEAR, view, display, look, 1.0f, 1.0f); /* Create shader descriptions. */ if (processor_to_scene_linear && processor_to_display) { GpuShaderDescRcPtr shaderdesc_to_scene_linear = GpuShaderDesc::CreateShaderDesc(); shaderdesc_to_scene_linear->setLanguage(GPU_LANGUAGE_GLSL_1_3); shaderdesc_to_scene_linear->setFunctionName("OCIO_to_scene_linear"); shaderdesc_to_scene_linear->setResourcePrefix("to_scene"); (*(ConstProcessorRcPtr *)processor_to_scene_linear) ->getDefaultGPUProcessor() ->extractGpuShaderInfo(shaderdesc_to_scene_linear); shaderdesc_to_scene_linear->finalize(); GpuShaderDescRcPtr shaderdesc_to_display = GpuShaderDesc::CreateShaderDesc(); shaderdesc_to_display->setLanguage(GPU_LANGUAGE_GLSL_1_3); shaderdesc_to_display->setFunctionName("OCIO_to_display"); shaderdesc_to_scene_linear->setResourcePrefix("to_display"); (*(ConstProcessorRcPtr *)processor_to_display) ->getDefaultGPUProcessor() ->extractGpuShaderInfo(shaderdesc_to_display); shaderdesc_to_display->finalize(); /* Create GPU shader and textures. */ if (createGPUTextures( display_shader.textures, shaderdesc_to_scene_linear, shaderdesc_to_display) && createGPUCurveMapping(display_shader.curvemap, curve_mapping_settings) && createGPUShader(display_shader.shader, display_shader.textures, shaderdesc_to_scene_linear, shaderdesc_to_display, use_curve_mapping)) { display_shader.valid = true; } } /* Free processors. */ if (processor_to_scene_linear) { OCIO_processorRelease(processor_to_scene_linear); } if (processor_to_display) { OCIO_processorRelease(processor_to_display); } return display_shader; } /** * Setup GPU contexts for a transform defined by processor using GLSL. * All LUT allocating baking and shader compilation happens here. * * Once this function is called, callee could start drawing images * using regular 2D texture. * * When all drawing is finished, gpuDisplayShaderUnbind must be called to * restore GPU context to its previous state. */ bool OCIOImpl::gpuDisplayShaderBind(OCIO_ConstConfigRcPtr *config, const char *input, const char *view, const char *display, const char *look, OCIO_CurveMappingSettings *curve_mapping_settings, const float scale, const float exponent, const float dither, const bool use_predivide, const bool use_overlay) { /* Get GPU shader from cache or create new one. */ OCIO_GPUDisplayShader &display_shader = getGPUDisplayShader( config, input, view, display, look, curve_mapping_settings); if (!display_shader.valid) { return false; } /* Verify the shader is valid. */ OCIO_GPUTextures &textures = display_shader.textures; OCIO_GPUShader &shader = display_shader.shader; OCIO_GPUCurveMappping &curvemap = display_shader.curvemap; /* Update and bind curve mapping data. */ if (curve_mapping_settings) { updateGPUCurveMapping(curvemap, curve_mapping_settings); GPU_uniformbuf_bind(curvemap.buffer, shader.ubo_bind); GPU_texture_bind(curvemap.texture, TEXTURE_SLOT_CURVE_MAPPING); } /* Bind textures to sampler units. Texture 0 is set by caller. * Uniforms have already been set for texture bind points.*/ if (!use_overlay) { /* Avoid missing binds. */ GPU_texture_bind(textures.dummy, TEXTURE_SLOT_OVERLAY); } for (int i = 0; i < textures.luts.size(); i++) { GPU_texture_bind(textures.luts[i].texture, TEXTURE_SLOT_LUTS_OFFSET + i); } /* TODO(fclem): remove remains of IMM. */ immBindShader(shader.shader); /* Bind Shader and set uniforms. */ // GPU_shader_bind(shader.shader); GPU_shader_uniform_float(shader.shader, shader.scale_loc, scale); GPU_shader_uniform_float(shader.shader, shader.exponent_loc, exponent); GPU_shader_uniform_float(shader.shader, shader.dither_loc, dither); GPU_shader_uniform_int(shader.shader, shader.overlay_loc, use_overlay); GPU_shader_uniform_int(shader.shader, shader.predivide_loc, use_predivide); return true; } void OCIOImpl::gpuDisplayShaderUnbind() { immUnbindProgram(); } void OCIOImpl::gpuCacheFree() { SHADER_CACHE.clear(); } /** \} */